6. 表达式

本章解释了 Python 中表达式元素的含义。

语法说明: 在本章和后续章节中,将使用扩展 BNF 符号来描述语法,而不是词法分析。当(一个语法规则的)一个备选形式具有以下形式时

name ::=  othername

并且没有给出语义,则此形式的 name 的语义与 othername 的语义相同。

6.1. 算术转换

当下面对算术运算符的描述使用短语“数值参数被转换为公共类型”时,这意味着内置类型的运算符实现按如下方式工作

  • 如果任一参数是复数,则另一个参数将被转换为复数;

  • 否则,如果任一参数是浮点数,则另一个参数将被转换为浮点数;

  • 否则,两者都必须是整数,并且不需要转换。

某些运算符(例如,字符串作为“%”运算符的左参数)适用一些额外的规则。扩展必须定义自己的转换行为。

6.2. 原子

原子是表达式的最基本元素。最简单的原子是标识符或字面量。用括号、方括号或花括号括起来的形式在语法上也被归类为原子。原子的语法如下

atom      ::=  identifier | literal | enclosure
enclosure ::=  parenth_form | list_display | dict_display | set_display
               | generator_expression | yield_atom

6.2.1. 标识符(名称)

作为原子的标识符是一个名称。有关词法定义,请参见第 标识符和关键字 节;有关命名和绑定的文档,请参见第 命名和绑定 节。

当名称绑定到某个对象时,对原子的求值将产生该对象。当名称未绑定时,尝试对其求值将引发 NameError 异常。

私有名称混淆: 当在类定义中文本出现的标识符以两个或多个下划线字符开头,并且不以两个或多个下划线字符结尾时,它被认为是该类的私有名称。私有名称在生成代码之前将被转换为更长的形式。转换会在名称前面插入类名,删除前导下划线并插入一个下划线。例如,在名为 Ham 的类中出现的标识符 __spam 将被转换为 _Ham__spam。此转换独立于标识符使用的语法上下文。如果转换后的名称过长(超过 255 个字符),则可能会发生实现定义的截断。如果类名仅由下划线组成,则不会进行转换。

6.2.2. 字面量

Python 支持字符串和字节字面量以及各种数值字面量

literal ::=  stringliteral | bytesliteral
             | integer | floatnumber | imagnumber

字面量的求值会生成给定类型(字符串、字节、整数、浮点数、复数)的具有给定值的某个对象。对于浮点数和虚数(复数)字面量,该值可能被近似。有关详细信息,请参见部分 字面量

所有字面量都对应于不可变数据类型,因此对象的标识不如其值重要。对具有相同值的字面量的多次求值(无论是程序文本中的相同出现,还是不同的出现)可能会获得同一个对象,也可能会获得具有相同值的不同的对象。

6.2.3. 带括号的形式

带括号的形式是一个可选的表达式列表,用括号括起来

parenth_form ::=  "(" [starred_expression] ")"

带括号的表达式列表会生成该表达式列表所生成的任何内容:如果列表至少包含一个逗号,则它会生成一个元组;否则,它会生成构成表达式列表的单个表达式。

一对空括号会生成一个空元组对象。由于元组是不可变的,因此与字面量相同的规则适用(即,空元组的两次出现可能生成同一个对象,也可能生成不同的对象)。

请注意,元组不是由括号形成的,而是由逗号形成的。唯一的例外是空元组,它需要括号——在表达式中允许不带括号的“空”会导致歧义,并允许常见的拼写错误不被捕获。

6.2.4. 列表、集合和字典的显示

为了构造列表、集合或字典,Python 提供了名为“显示”的特殊语法,它们各有两种形式

  • 要么显式列出容器内容,要么

  • 通过一组循环和过滤指令(称为推导式)来计算它们。

推导式的常用语法元素是

comprehension ::=  assignment_expression comp_for
comp_for      ::=  ["async"] "for" target_list "in" or_test [comp_iter]
comp_iter     ::=  comp_for | comp_if
comp_if       ::=  "if" or_test [comp_iter]

推导式由一个表达式组成,后面至少跟一个for 子句,以及零个或多个forif 子句。在这种情况下,新容器的元素是通过将每个forif 子句视为一个块(从左到右嵌套),并在每次到达最内层块时求值表达式来生成元素。

但是,除了最左侧for 子句中的可迭代表达式之外,推导式是在一个单独的隐式嵌套作用域中执行的。这确保了在目标列表中分配的名称不会“泄漏”到封闭作用域中。

最左侧for 子句中的可迭代表达式直接在封闭作用域中求值,然后作为参数传递给隐式嵌套作用域。后续的for 子句以及最左侧for 子句中的任何过滤器条件都无法在封闭作用域中求值,因为它们可能依赖于从最左侧可迭代对象获得的值。例如:[x*y for x in range(10) for y in range(x, x+10)]

为了确保推导式始终生成适当类型的容器,在隐式嵌套作用域中禁止使用yieldyield from 表达式。

从 Python 3.6 开始,在 async def 函数中,可以使用 async for 子句迭代 异步迭代器。在 async def 函数中的推导式可以包含在引导表达式后的 forasync for 子句,可以包含额外的 forasync for 子句,还可以使用 await 表达式。如果推导式包含 async for 子句或 await 表达式或其他异步推导式,则称为 异步推导式。异步推导式可能会挂起它所出现的协程函数的执行。另请参见 PEP 530

在版本 3.6 中添加: 引入了异步推导式。

在版本 3.8 中更改: yieldyield from 在隐式嵌套作用域中被禁止。

在版本 3.11 中更改: 异步推导式现在允许在异步函数中的推导式内使用。外部推导式隐式地变为异步的。

6.2.5. 列表显示

列表显示是一系列可能为空的表达式,用方括号括起来

list_display ::=  "[" [starred_list | comprehension] "]"

列表显示生成一个新的列表对象,其内容由表达式列表或推导式指定。当提供逗号分隔的表达式列表时,其元素从左到右进行计算,并按该顺序放入列表对象中。当提供推导式时,列表由推导式产生的元素构成。

6.2.6. 集合显示

集合显示用花括号表示,通过缺少分隔键和值的冒号来区分字典显示

set_display ::=  "{" (starred_list | comprehension) "}"

集合显示生成一个新的可变集合对象,其内容由表达式序列或推导式指定。当提供逗号分隔的表达式列表时,其元素从左到右进行计算并添加到集合对象中。当提供推导式时,集合由推导式产生的元素构成。

空集合不能用 {} 构建;此字面量构建一个空字典。

6.2.7. 字典显示

字典显示是一系列可能为空的字典项(键/值对),用花括号括起来

dict_display       ::=  "{" [dict_item_list | dict_comprehension] "}"
dict_item_list     ::=  dict_item ("," dict_item)* [","]
dict_item          ::=  expression ":" expression | "**" or_expr
dict_comprehension ::=  expression ":" expression comp_for

字典显示生成一个新的字典对象。

如果给出逗号分隔的字典项序列,则从左到右计算它们以定义字典的条目:每个键对象用作字典中的键以存储相应的键值。这意味着你可以在字典项列表中多次指定相同的键,最终字典中该键的值将是最后给出的值。

双星号 ** 表示 字典解包。其操作数必须是 映射。每个映射项都将添加到新字典中。后面的值将替换由前面的字典项和前面的字典解包设置的值。

在版本 3.5 中添加: 解包到字典显示中,最初由 PEP 448 提出。

与列表和集合推导不同,字典推导需要两个用冒号分隔的表达式,后面跟着通常的“for”和“if”子句。当推导运行时,生成的键值元素将按它们产生的顺序插入到新的字典中。

键值的类型限制在前面标准类型层次结构部分中列出。(概括地说,键类型应该是可散列的,这排除了所有可变对象。)不会检测到重复键之间的冲突;给定键值存储的最后一个值(在显示中文本上最右边的值)将生效。

在 3.8 版中变更: 在 Python 3.8 之前,在字典推导中,键和值的评估顺序没有明确定义。在 CPython 中,值在键之前被评估。从 3.8 开始,键在值之前被评估,如PEP 572中所提议的。

6.2.8. 生成器表达式

生成器表达式是括号中的紧凑生成器表示法

generator_expression ::=  "(" expression comp_for ")"

生成器表达式生成一个新的生成器对象。它的语法与推导相同,只是它被括号而不是方括号或花括号括起来。

当为生成器对象调用__next__()方法时,生成器表达式中使用的变量会被延迟评估(与普通生成器的方式相同)。但是,最左侧for子句中的可迭代表达式会立即被评估,因此由它产生的错误将在定义生成器表达式的地方发出,而不是在检索第一个值的地方发出。随后的for子句和最左侧for子句中的任何过滤器条件都不能在封闭作用域中被评估,因为它们可能依赖于从最左侧可迭代对象获得的值。例如:(x*y for x in range(10) for y in range(x, x+10))

对于只有一个参数的调用,可以省略括号。有关详细信息,请参见调用部分。

为了避免干扰生成器表达式本身的预期操作,yieldyield from表达式在隐式定义的生成器中是被禁止的。

如果生成器表达式包含async for子句或await表达式,则它被称为异步生成器表达式。异步生成器表达式返回一个新的异步生成器对象,它是一个异步迭代器(参见异步迭代器)。

在 3.6 版中添加: 引入了异步生成器表达式。

在 3.7 版中变更: 在 Python 3.7 之前,异步生成器表达式只能出现在async def协程中。从 3.7 开始,任何函数都可以使用异步生成器表达式。

在版本 3.8 中更改: yieldyield from 在隐式嵌套作用域中被禁止。

6.2.9. yield 表达式

yield_atom       ::=  "(" yield_expression ")"
yield_from       ::=  "yield" "from" expression
yield_expression ::=  "yield" expression_list | yield_from

yield 表达式用于定义生成器函数或异步生成器函数,因此只能在函数定义体中使用。在函数体中使用 yield 表达式会导致该函数成为生成器函数,在async def函数体中使用它会导致该协程函数成为异步生成器函数。例如

def gen():  # defines a generator function
    yield 123

async def agen(): # defines an asynchronous generator function
    yield 123

由于它们对包含作用域的副作用,yield表达式不允许作为用于实现推导和生成器表达式的隐式定义作用域的一部分。

在 3.8 版中变更: yield 表达式在用于实现推导和生成器表达式的隐式嵌套作用域中被禁止。

生成器函数将在下面描述,而异步生成器函数将在异步生成器函数部分单独描述。

当调用生成器函数时,它将返回一个称为生成器的迭代器。该生成器随后控制生成器函数的执行。执行从调用生成器方法之一时开始。此时,执行将继续到第一个 yield 表达式,在那里它将再次暂停,将expression_list的值返回给生成器的调用者,或者如果省略了expression_list,则返回None。通过暂停,我们的意思是所有局部状态都将保留,包括局部变量的当前绑定、指令指针、内部评估堆栈以及任何异常处理的状态。当通过调用生成器方法之一来恢复执行时,该函数可以继续执行,就好像 yield 表达式只是另一个外部调用一样。恢复后 yield 表达式的值取决于恢复执行的方法。如果使用__next__()(通常通过for或内置的next()),则结果为None。否则,如果使用send(),则结果将是传递给该方法的值。

所有这些使得生成器函数非常类似于协程;它们多次产生,它们具有多个入口点,并且它们的执行可以暂停。唯一的区别是生成器函数无法控制在产生后执行应继续的位置;控制始终转移到生成器的调用者。

yield 表达式允许在try结构中的任何位置使用。如果在生成器被最终确定之前(通过达到零引用计数或被垃圾回收)没有恢复生成器,则生成器迭代器的close()方法将被调用,允许任何待处理的finally子句执行。

当使用yield from <expr>时,提供的表达式必须是可迭代的。通过迭代该可迭代对象产生的值将直接传递给当前生成器方法的调用者。使用send()传递的任何值以及使用throw()传递的任何异常都将传递给底层迭代器(如果它具有适当的方法)。如果不是这种情况,则send()将引发AttributeErrorTypeError,而throw()将立即引发传递的异常。

当底层迭代器完成时,引发的StopIteration实例的value属性将成为 yield 表达式的值。它可以在引发StopIteration时显式设置,或者当子迭代器是生成器时自动设置(通过从子生成器返回一个值)。

版本 3.3 中的变更: 添加了 yield from <expr> 用于将控制流委托给子迭代器。

当 yield 表达式是赋值语句右侧的唯一表达式时,可以省略括号。

另请参阅

PEP 255 - 简单生成器

关于向 Python 添加生成器和 yield 语句的提案。

PEP 342 - 通过增强生成器实现协程

关于增强生成器 API 和语法的提案,使其可用作简单的协程。

PEP 380 - 委托给子生成器的语法

关于引入 yield_from 语法的提案,使委托给子生成器变得容易。

PEP 525 - 异步生成器

该提案扩展了 PEP 492,通过向协程函数添加生成器功能。

6.2.9.1. 生成器迭代器方法

本节介绍生成器迭代器的方法。它们可用于控制生成器函数的执行。

请注意,当生成器正在执行时,调用以下任何生成器方法都会引发 ValueError 异常。

generator.__next__()

启动生成器函数的执行,或从上次执行的 yield 表达式处恢复执行。当使用 __next__() 方法恢复生成器函数时,当前 yield 表达式始终计算为 None。然后执行继续到下一个 yield 表达式,生成器在那里再次挂起,并且 expression_list 的值将返回给 __next__() 的调用者。如果生成器退出而没有产生另一个值,则会引发 StopIteration 异常。

此方法通常是隐式调用的,例如,由 for 循环或内置的 next() 函数调用。

generator.send(value)

恢复执行并将一个值“发送”到生成器函数中。value 参数将成为当前 yield 表达式的结果。 send() 方法返回生成器产生的下一个值,或者如果生成器退出而没有产生另一个值,则引发 StopIteration。当调用 send() 来启动生成器时,必须使用 None 作为参数调用它,因为没有 yield 表达式可以接收该值。

generator.throw(value)
generator.throw(type[, value[, traceback]])

在生成器暂停的位置引发异常,并返回生成器函数产生的下一个值。如果生成器在没有产生另一个值的情况下退出,则会引发 StopIteration 异常。如果生成器函数没有捕获传入的异常,或者引发了不同的异常,则该异常会传播到调用者。

在典型使用中,这与使用 raise 关键字的方式类似,使用单个异常实例调用。

但是,为了向后兼容,支持第二个签名,遵循 Python 旧版本中的约定。type 参数应为异常类,value 应为异常实例。如果未提供 value,则调用 type 构造函数以获取实例。如果提供了 traceback,则将其设置在异常上,否则可能会清除存储在 value 中的任何现有 __traceback__ 属性。

在 3.12 版本中变更: 第二个签名 (type[, value[, traceback]]) 已被弃用,可能会在 Python 的未来版本中移除。

generator.close()

在生成器函数暂停的位置引发 GeneratorExit。如果生成器函数随后正常退出,已经关闭,或者引发 GeneratorExit(通过不捕获异常),则 close 返回其调用者。如果生成器产生一个值,则会引发 RuntimeError。如果生成器引发任何其他异常,则会将其传播到调用者。 close() 如果生成器由于异常或正常退出而已经退出,则不会执行任何操作。

6.2.9.2. 示例

这是一个演示生成器和生成器函数行为的简单示例

>>> def echo(value=None):
...     print("Execution starts when 'next()' is called for the first time.")
...     try:
...         while True:
...             try:
...                 value = (yield value)
...             except Exception as e:
...                 value = e
...     finally:
...         print("Don't forget to clean up when 'close()' is called.")
...
>>> generator = echo(1)
>>> print(next(generator))
Execution starts when 'next()' is called for the first time.
1
>>> print(next(generator))
None
>>> print(generator.send(2))
2
>>> generator.throw(TypeError, "spam")
TypeError('spam',)
>>> generator.close()
Don't forget to clean up when 'close()' is called.

有关使用 yield from 的示例,请参阅“Python 中的新增功能”中的 PEP 380:委托给子生成器的语法

6.2.9.3. 异步生成器函数

在使用 async def 定义的函数或方法中存在 yield 表达式,进一步将该函数定义为 异步生成器 函数。

当调用异步生成器函数时,它会返回一个称为异步生成器对象的异步迭代器。然后,该对象控制生成器函数的执行。异步生成器对象通常在协程函数中的 async for 语句中使用,类似于生成器对象在 for 语句中的使用方式。

调用异步生成器的方法会返回一个 可等待 对象,当这个对象被等待时,执行开始。此时,执行将继续到第一个 yield 表达式,在那里它再次被挂起,将 expression_list 的值返回给等待的协程。与生成器一样,挂起意味着所有局部状态都将保留,包括局部变量的当前绑定、指令指针、内部评估堆栈以及任何异常处理的状态。当执行通过等待异步生成器方法返回的下一个对象来恢复时,函数可以像 yield 表达式只是另一个外部调用一样继续执行。恢复后 yield 表达式的值取决于恢复执行的方法。如果使用 __anext__(),则结果为 None。否则,如果使用 asend(),则结果将是传递给该方法的值。

如果异步生成器恰好通过 break、调用者任务被取消或其他异常而过早退出,则生成器的异步清理代码将运行,并可能在意外的上下文中引发异常或访问上下文变量——可能在它依赖的任务的生命周期之后,或者在事件循环关闭时调用异步生成器垃圾回收钩子。为了防止这种情况,调用者必须通过调用 aclose() 方法显式关闭异步生成器,以完成生成器并最终将其从事件循环中分离。

在异步生成器函数中,yield 表达式可以在 try 结构中的任何位置使用。但是,如果异步生成器在完成之前(通过达到零引用计数或被垃圾回收)没有恢复,那么在 try 结构中的 yield 表达式可能会导致无法执行挂起的 finally 子句。在这种情况下,运行异步生成器的事件循环或调度程序有责任调用异步生成器迭代器的 aclose() 方法并运行生成的协程对象,从而允许任何挂起的 finally 子句执行。

为了在事件循环终止时处理完成,事件循环应该定义一个 *finalizer* 函数,该函数接受一个异步生成器迭代器,并可能调用 aclose() 并执行协程。可以通过调用 sys.set_asyncgen_hooks() 来注册此 *finalizer*。当第一次迭代时,异步生成器迭代器将存储注册的 *finalizer*,以便在完成时调用。有关 *finalizer* 方法的参考示例,请参阅 Lib/asyncio/base_events.pyasyncio.Loop.shutdown_asyncgens 的实现。

表达式 yield from <expr> 在异步生成器函数中使用时是语法错误。

6.2.9.4. 异步生成器迭代器方法

本节描述异步生成器迭代器的方法,这些方法用于控制生成器函数的执行。

协程 agen.__anext__()

返回一个可等待对象,当运行时开始执行异步生成器或从上次执行的 yield 表达式处恢复执行。当异步生成器函数使用 __anext__() 方法恢复时,当前 yield 表达式在返回的可等待对象中始终计算为 None,当运行时将继续执行到下一个 yield 表达式。yield 表达式的 表达式列表 的值是完成的协程引发的 StopIteration 异常的值。如果异步生成器在不产生另一个值的情况下退出,则可等待对象将改为引发 StopAsyncIteration 异常,表示异步迭代已完成。

此方法通常由 async for 循环隐式调用。

协程 agen.asend(value)

返回一个可等待对象,当运行时恢复异步生成器的执行。与生成器的 send() 方法一样,这会将一个值“发送”到异步生成器函数中,并且 value 参数将成为当前 yield 表达式的结果。由 asend() 方法返回的可等待对象将返回生成器产生的下一个值作为引发的 StopIteration 的值,或者如果异步生成器在不产生另一个值的情况下退出,则引发 StopAsyncIteration。当调用 asend() 来启动异步生成器时,必须使用 None 作为参数调用它,因为没有 yield 表达式可以接收该值。

协程 agen.athrow(value)
协程 agen.athrow(type[, value[, traceback]])

返回一个可等待对象,它在异步生成器暂停的位置引发类型为 type 的异常,并将生成器函数产生的下一个值作为引发的 StopIteration 异常的值返回。如果异步生成器在不产生另一个值的情况下退出,则可等待对象将引发 StopAsyncIteration 异常。如果生成器函数没有捕获传入的异常,或者引发了不同的异常,那么当运行可等待对象时,该异常将传播到可等待对象的调用者。

在 3.12 版本中变更: 第二个签名 (type[, value[, traceback]]) 已被弃用,可能会在 Python 的未来版本中移除。

协程 agen.aclose()

返回一个可等待对象,当运行时,将在异步生成器函数暂停的位置抛出一个 GeneratorExit 异常。如果异步生成器函数随后正常退出,已经关闭,或者抛出 GeneratorExit(通过不捕获异常),那么返回的可等待对象将抛出一个 StopIteration 异常。后续对异步生成器的任何调用返回的任何可等待对象都将抛出一个 StopAsyncIteration 异常。如果异步生成器生成一个值,则可等待对象将抛出一个 RuntimeError 异常。如果异步生成器抛出任何其他异常,则该异常将传播到可等待对象的调用者。如果异步生成器已经由于异常或正常退出而退出,那么对 aclose() 的进一步调用将返回一个不执行任何操作的可等待对象。

6.3. 基本表达式

基本表达式表示语言中最紧密绑定的操作。它们的语法是

primary ::=  atom | attributeref | subscription | slicing | call

6.3.1. 属性引用

属性引用是一个基本表达式,后跟一个句点和一个名称

attributeref ::=  primary "." identifier

基本表达式必须计算为支持属性引用的类型的对象,大多数对象都支持。然后要求该对象生成名为标识符的属性。生成的类型和值由对象决定。对同一个属性引用的多次计算可能会产生不同的对象。

此生成可以通过覆盖 __getattribute__() 方法或 __getattr__() 方法来定制。首先调用 __getattribute__() 方法,如果属性不可用,则返回一个值或抛出 AttributeError 异常。

如果抛出 AttributeError 异常,并且对象具有 __getattr__() 方法,则该方法将作为回退调用。

6.3.2. 下标

容器类 实例的下标通常将从容器中选择一个元素。对 泛型类 的下标通常将返回一个 GenericAlias 对象。

subscription ::=  primary "[" expression_list "]"

当对对象进行下标时,解释器将计算基本表达式和表达式列表。

基本表达式必须计算为支持下标的对象。对象可以通过定义 __getitem__()__class_getitem__() 中的一个或两个方法来支持下标。当对基本表达式进行下标时,表达式列表的计算结果将传递给这些方法之一。有关何时调用 __class_getitem__ 而不是 __getitem__ 的更多详细信息,请参见 __class_getitem__ 与 __getitem__

如果表达式列表包含至少一个逗号,它将计算为一个 tuple,其中包含表达式列表的项目。否则,表达式列表将计算为列表唯一成员的值。

对于内置对象,有两种类型的对象支持通过 __getitem__() 进行下标

  1. 映射。如果基本表达式是一个 映射,则表达式列表必须计算为一个对象,其值为映射的键之一,并且下标选择映射中对应于该键的值。内置映射类的示例是 dict 类。

  2. 序列。如果主项是 序列,则表达式列表必须计算为 intslice(如下一节所述)。内置序列类的示例包括 strlisttuple 类。

正式语法没有为 序列 中的负索引提供特殊规定。但是,内置序列都提供了一个 __getitem__() 方法,该方法通过将序列的长度加到索引来解释负索引,例如,x[-1] 选择 x 的最后一个项目。结果值必须为小于序列中项目数量的非负整数,并且下标选择索引为该值的项目(从零开始计数)。由于对负索引和切片的支持发生在对象的 __getitem__() 方法中,覆盖此方法的子类将需要显式添加该支持。

字符串 是一种特殊的序列,其项目是字符。字符不是单独的数据类型,而是只有一个字符的字符串。

6.3.3. 切片

切片选择序列对象(例如,字符串、元组或列表)中的项目范围。切片可以用作表达式,也可以用作赋值或 del 语句中的目标。切片的语法

slicing      ::=  primary "[" slice_list "]"
slice_list   ::=  slice_item ("," slice_item)* [","]
slice_item   ::=  expression | proper_slice
proper_slice ::=  [lower_bound] ":" [upper_bound] [ ":" [stride] ]
lower_bound  ::=  expression
upper_bound  ::=  expression
stride       ::=  expression

这里正式语法存在歧义:任何看起来像表达式列表的东西也看起来像切片列表,因此任何下标都可以解释为切片。为了不进一步复杂化语法,通过定义在这种情况下,对下标的解释优先于对切片的解释来消除歧义(如果切片列表不包含任何正确的切片,则为这种情况)。

切片的语义如下。主项使用与普通下标相同的 __getitem__() 方法进行索引,其键由切片列表构造,如下所示。如果切片列表至少包含一个逗号,则键是一个包含切片项转换的元组;否则,单个切片项的转换是键。表达式为切片项的转换是该表达式。正确切片的转换是一个切片对象(参见部分 标准类型层次结构),其 startstopstep 属性分别是作为下限、上限和步长给出的表达式的值,用 None 代替缺失的表达式。

6.3.4. 调用

调用使用可能为空的 参数 系列调用可调用对象(例如,函数)。

call                 ::=  primary "(" [argument_list [","] | comprehension] ")"
argument_list        ::=  positional_arguments ["," starred_and_keywords]
                            ["," keywords_arguments]
                          | starred_and_keywords ["," keywords_arguments]
                          | keywords_arguments
positional_arguments ::=  positional_item ("," positional_item)*
positional_item      ::=  assignment_expression | "*" expression
starred_and_keywords ::=  ("*" expression | keyword_item)
                          ("," "*" expression | "," keyword_item)*
keywords_arguments   ::=  (keyword_item | "**" expression)
                          ("," keyword_item | "," "**" expression)*
keyword_item         ::=  identifier "=" expression

在位置参数和关键字参数之后可以存在可选的尾随逗号,但这不会影响语义。

主项必须计算为可调用对象(用户定义函数、内置函数、内置对象的 方法、类对象、类实例的方法以及所有具有 __call__() 方法的对象都是可调用的)。所有参数表达式都在尝试调用之前进行计算。有关正式 参数 列表的语法,请参阅部分 函数定义

如果存在关键字参数,则首先将其转换为位置参数,如下所示。首先,为形式参数创建一个未填充槽位的列表。如果有 N 个位置参数,则将它们放置在第一个 N 个槽位中。接下来,对于每个关键字参数,使用标识符来确定相应的槽位(如果标识符与第一个形式参数名称相同,则使用第一个槽位,依此类推)。如果该槽位已被填充,则会引发 TypeError 异常。否则,将参数放置在槽位中,填充它(即使表达式为 None,它也会填充槽位)。当所有参数都已处理后,未填充的槽位将使用函数定义中的相应默认值填充。(默认值在函数定义时计算一次;因此,诸如列表或字典之类的可变对象用作默认值将由所有不为相应槽位指定参数值的调用共享;这通常应该避免。)如果存在任何未填充的槽位,而没有指定默认值,则会引发 TypeError 异常。否则,填充的槽位列表将用作调用的参数列表。

CPython 实现细节:实现可以提供内置函数,其位置参数没有名称,即使它们出于文档目的而被“命名”,因此不能通过关键字提供。在 CPython 中,对于使用 PyArg_ParseTuple() 解析其参数的用 C 实现的函数,情况就是这样。

如果位置参数多于形式参数槽位,则会引发 TypeError 异常,除非存在使用语法 *identifier 的形式参数;在这种情况下,该形式参数将接收一个包含多余位置参数的元组(如果不存在多余位置参数,则为一个空元组)。

如果任何关键字参数与形式参数名称不对应,则会引发 TypeError 异常,除非存在使用语法 **identifier 的形式参数;在这种情况下,该形式参数将接收一个包含多余关键字参数的字典(使用关键字作为键,参数值作为对应值),或者一个(新的)空字典,如果不存在多余关键字参数。

如果语法 *expression 出现在函数调用中,则 expression 必须计算为一个 可迭代对象。来自这些可迭代对象的元素将被视为附加的位置参数。对于调用 f(x1, x2, *y, x3, x4),如果 y 计算为一个序列 y1,…,yM,则这等效于使用 M+4 个位置参数 x1x2y1,…,yMx3x4 的调用。

这导致的结果是,尽管 *expression 语法可能出现在显式关键字参数之后,但它是在关键字参数(以及任何 **expression 参数 - 见下文)之前处理的。所以

>>> def f(a, b):
...     print(a, b)
...
>>> f(b=1, *(2,))
2 1
>>> f(a=1, *(2,))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() got multiple values for keyword argument 'a'
>>> f(1, *(2,))
1 2

在同一个调用中使用关键字参数和 *expression 语法并不常见,因此在实践中这种混淆并不经常出现。

如果函数调用中出现了语法 **expression,则 expression 必须计算为一个 映射,其内容被视为额外的关键字参数。如果与键匹配的参数已经获得了值(通过显式关键字参数或来自另一个解包),则会引发 TypeError 异常。

当使用 **expression 时,此映射中的每个键都必须是字符串。映射中的每个值都将分配给第一个符合关键字分配资格的正式参数,其名称等于键。键不必是 Python 标识符(例如 "max-temp °F" 是可以接受的,尽管它不会与任何可以声明的正式参数匹配)。如果没有与正式参数匹配,则键值对将由 ** 参数收集(如果有),否则会引发 TypeError 异常。

使用语法 *identifier**identifier 的正式参数不能用作位置参数槽或关键字参数名称。

在 3.5 版本中更改: 函数调用接受任意数量的 *** 解包,位置参数可以跟随可迭代解包 (*),关键字参数可以跟随字典解包 (**)。最初由 PEP 448 提出。

调用始终返回某个值,可能是 None,除非它引发异常。此值的计算方式取决于可调用对象的类型。

如果是:

用户定义的函数

函数的代码块将被执行,并传递给它参数列表。代码块将首先做的是将正式参数绑定到参数;这在第 函数定义 节中描述。当代码块执行 return 语句时,这将指定函数调用的返回值。

内置函数或方法

结果取决于解释器;有关内置函数和方法的描述,请参见 内置函数

类对象

将返回该类的新的实例。

类实例方法

将调用相应的用户定义函数,其参数列表比调用的参数列表长一个:实例成为第一个参数。

类实例

类必须定义一个 __call__() 方法;然后效果与调用该方法相同。

6.4. 等待表达式

可等待 对象上暂停 协程 的执行。只能在 协程函数 中使用。

await_expr ::=  "await" primary

在 3.5 版本中添加。

6.5. 幂运算符

幂运算符比其左侧的单目运算符绑定得更紧密;它比其右侧的单目运算符绑定得更松散。语法是

power ::=  (await_expr | primary) ["**" u_expr]

因此,在没有括号的幂运算符和单目运算符序列中,运算符是从右到左计算的(这不会限制操作数的计算顺序):-1**2 的结果为 -1

当使用两个参数调用时,幂运算符与内置的 pow() 函数具有相同的语义:它产生其左侧参数的右侧参数的幂。数值参数首先转换为公共类型,结果为该类型。

对于整型操作数,结果类型与操作数相同,除非第二个参数为负数;在这种情况下,所有参数都将转换为浮点数,并返回浮点型结果。例如,10**2 返回 100,但 10**-2 返回 0.01

0.0 乘以负数幂会导致 ZeroDivisionError。将负数乘以分数幂会导致 complex 数。(在早期版本中,它会引发 ValueError。)

可以使用特殊的 __pow__()__rpow__() 方法自定义此操作。

6.6. 一元算术和位运算

所有一元算术和位运算具有相同的优先级

u_expr ::=  power | "-" u_expr | "+" u_expr | "~" u_expr

一元 -(减号)运算符返回其数值参数的否定;该操作可以使用 __neg__() 特殊方法重写。

一元 +(加号)运算符返回其数值参数不变;该操作可以使用 __pos__() 特殊方法重写。

一元 ~(反转)运算符返回其整型参数的按位反转。 x 的按位反转定义为 -(x+1)。它仅适用于整型数字或重写了 __invert__() 特殊方法的自定义对象。

在这三种情况下,如果参数没有正确的类型,则会引发 TypeError 异常。

6.7. 二元算术运算

二元算术运算具有传统的优先级。请注意,其中一些运算也适用于某些非数值类型。除了幂运算符之外,只有两个级别,一个用于乘法运算符,一个用于加法运算符

m_expr ::=  u_expr | m_expr "*" u_expr | m_expr "@" m_expr |
            m_expr "//" u_expr | m_expr "/" u_expr |
            m_expr "%" u_expr
a_expr ::=  m_expr | a_expr "+" m_expr | a_expr "-" m_expr

*(乘法)运算符返回其参数的乘积。参数必须都是数字,或者一个参数必须是整数,另一个参数必须是序列。在前一种情况下,数字将转换为公共类型,然后相乘。在后一种情况下,将执行序列重复;负的重复因子将产生一个空序列。

可以使用特殊的 __mul__()__rmul__() 方法自定义此操作。

@(at)运算符旨在用于矩阵乘法。没有内置的 Python 类型实现此运算符。

可以使用特殊的 __matmul__()__rmatmul__() 方法自定义此操作。

在 3.5 版本中添加。

除法运算符 / 和地板除法运算符 // 返回其参数的商。数值参数首先被转换为相同类型。整数的除法运算结果为浮点数,而整数的地板除法运算结果为整数;结果是数学除法运算的结果应用了“地板”函数。除以零会引发 ZeroDivisionError 异常。

除法运算可以使用特殊的 __truediv__()__rtruediv__() 方法进行自定义。地板除法运算可以使用特殊的 __floordiv__()__rfloordiv__() 方法进行自定义。

取模运算符 % 返回第一个参数除以第二个参数的余数。数值参数首先被转换为相同类型。右操作数为零会引发 ZeroDivisionError 异常。参数可以是浮点数,例如 3.14%0.7 等于 0.34(因为 3.14 等于 4*0.7 + 0.34)。取模运算符始终返回一个与第二个操作数符号相同的(或零)结果;结果的绝对值严格小于第二个操作数的绝对值 [1]

地板除法运算符和取模运算符通过以下恒等式连接:x == (x//y)*y + (x%y)。地板除法和取模也与内置函数 divmod() 连接:divmod(x, y) == (x//y, x%y)[2]

除了对数字执行取模运算外,% 运算符也被字符串对象重载以执行旧式字符串格式化(也称为插值)。字符串格式化的语法在 Python 库参考中的 printf-style String Formatting 部分进行了描述。

取模运算可以使用特殊的 __mod__()__rmod__() 方法进行自定义。

地板除法运算符、取模运算符和 divmod() 函数没有为复数定义。相反,如果合适,请使用 abs() 函数将其转换为浮点数。

加法运算符 + 返回其参数的总和。参数必须都是数字或都是相同类型的序列。在前一种情况下,数字被转换为相同类型,然后加在一起。在后一种情况下,序列被连接起来。

此操作可以使用特殊的 __add__()__radd__() 方法进行自定义。

减法运算符 - 返回其参数的差。数值参数首先被转换为相同类型。

此操作可以使用特殊的 __sub__()__rsub__() 方法进行自定义。

6.8. 移位运算

移位运算的优先级低于算术运算。

shift_expr ::=  a_expr | shift_expr ("<<" | ">>") a_expr

这些运算符接受整数作为参数。它们将第一个参数向左或向右移动由第二个参数指定的位数。

左移运算可以使用特殊的 __lshift__()__rlshift__() 方法进行自定义。右移运算可以使用特殊的 __rshift__()__rrshift__() 方法进行自定义。

右移 n 位定义为对 pow(2,n) 进行地板除。左移 n 位定义为乘以 pow(2,n)

6.9. 二进制按位运算

三个按位运算符中的每一个都有不同的优先级。

and_expr ::=  shift_expr | and_expr "&" shift_expr
xor_expr ::=  and_expr | xor_expr "^" and_expr
or_expr  ::=  xor_expr | or_expr "|" xor_expr

& 运算符生成其参数的按位 AND,这些参数必须是整数,或者其中一个必须是覆盖了 __and__()__rand__() 特殊方法的自定义对象。

^ 运算符生成其参数的按位 XOR(异或),这些参数必须是整数,或者其中一个必须是覆盖了 __xor__()__rxor__() 特殊方法的自定义对象。

| 运算符生成其参数的按位(包含)OR,这些参数必须是整数,或者其中一个必须是覆盖了 __or__()__ror__() 特殊方法的自定义对象。

6.10. 比较

与 C 不同,Python 中的所有比较运算符都具有相同的优先级,该优先级低于任何算术、移位或按位运算符的优先级。同样与 C 不同,a < b < c 这样的表达式具有数学上惯用的解释。

comparison    ::=  or_expr (comp_operator or_expr)*
comp_operator ::=  "<" | ">" | "==" | ">=" | "<=" | "!="
                   | "is" ["not"] | ["not"] "in"

比较产生布尔值:TrueFalse。自定义的 丰富比较方法 可能会返回非布尔值。在这种情况下,Python 将在布尔上下文中对该值调用 bool()

比较可以任意地链接,例如,x < y <= z 等效于 x < y and y <= z,除了 y 只被评估一次(但在两种情况下,当发现 x < y 为假时,z 根本不会被评估)。

形式上,如果 abc、…、yz 是表达式,而 op1op2、…、opN 是比较运算符,那么 a op1 b op2 c ... y opN z 等效于 a op1 b and b op2 c and ... y opN z,除了每个表达式最多被评估一次。

请注意,a op1 b op2 c 并不意味着对 *a* 和 *c* 进行任何比较,因此,例如,x < y > z 是完全合法的(尽管可能不太美观)。

6.10.1. 值比较

运算符 <>==>=<=!= 比较两个对象的**值**。这些对象不需要具有相同的类型。

对象、值和类型 章指出,对象除了类型和标识之外,还具有值。在 Python 中,对象的**值**是一个相当抽象的概念:例如,没有用于访问对象值的规范方法。此外,也没有要求对象的**值**应该以特定方式构造,例如由其所有数据属性组成。比较运算符实现了对对象**值**的特定概念。可以将它们视为通过比较实现间接定义对象的**值**。

由于所有类型都是 object 的(直接或间接)子类型,因此它们从 object 继承默认的比较行为。类型可以通过实现诸如 __lt__() 之类的**丰富比较方法**来定制其比较行为,如 基本定制 中所述。

默认的相等比较行为(==!=)基于对象的**标识**。因此,具有相同**标识**的实例的相等比较结果为相等,而具有不同**标识**的实例的相等比较结果为不相等。这种默认行为的动机是希望所有对象都应该是自反的(即 x is y 意味着 x == y)。

默认的顺序比较(<><=>=)未提供;尝试会引发 TypeError。这种默认行为的动机是缺乏与相等性类似的不变性。

默认相等比较的行为,即具有不同**标识**的实例始终不相等,可能与需要对对象**值**和基于**值**的相等性有合理定义的类型的需求相矛盾。此类类型需要定制其比较行为,事实上,许多内置类型已经做到了这一点。

以下列表描述了最重要的内置类型的比较行为。

  • 内置数值类型(数值类型 — int、float、complex)和标准库类型 fractions.Fractiondecimal.Decimal 的数字可以在其类型之间进行比较,限制是复数不支持顺序比较。在所涉及类型的范围内,它们以数学上(算法上)正确的方式进行比较,不会损失精度。

    非数字值 float('NaN')decimal.Decimal('NaN') 是特殊的。任何将数字与非数字值进行的排序比较都将返回假。一个反直觉的含义是非数字值不等于自身。例如,如果 x = float('NaN'),则 3 < xx < 3x == x 都为假,而 x != x 为真。此行为符合 IEEE 754 标准。

  • NoneNotImplemented 是单例。 PEP 8 建议对单例的比较始终使用 isis not,而不是使用相等运算符。

  • 二进制序列(bytesbytearray 的实例)可以在其类型内部和之间进行比较。它们使用其元素的数值进行字典序比较。

  • 字符串(str 的实例)使用其字符的数值 Unicode 代码点(内置函数 ord() 的结果)进行字典序比较。 [3]

    字符串和二进制序列不能直接比较。

  • 序列(tuplelistrange 的实例)只能在其类型内部进行比较,并且范围不支持顺序比较。这些类型之间的相等比较会导致不等,而这些类型之间的排序比较会引发 TypeError

    序列使用对应元素的比较进行字典序比较。内置容器通常假设相同对象等于自身。这使它们能够绕过相同对象的相等性测试,以提高性能并维护其内部不变性。

    内置集合之间的字典序比较按如下方式进行

    • 要使两个集合比较相等,它们必须是相同类型,具有相同的长度,并且每对对应元素必须比较相等(例如,[1,2] == (1,2) 为假,因为类型不同)。

    • 支持顺序比较的集合按其第一个不相等的元素的顺序排序(例如,[1,2,x] <= [1,2,y] 的值与 x <= y 相同)。如果不存在对应元素,则较短的集合按顺序排在前面(例如,[1,2] < [1,2,3] 为真)。

  • 映射(dict 的实例)只有在它们具有相同的 (key, value) 对时才相等。键和值的相等比较强制执行自反性。

    顺序比较(<><=>=)会引发 TypeError

  • 集合(setfrozenset 的实例)可以在其类型内和跨类型进行比较。

    它们定义了顺序比较运算符,表示子集和超集测试。这些关系没有定义全序(例如,两个集合 {1,2}{2,3} 不相等,也不是彼此的子集或超集)。因此,集合不适合作为依赖于全序的函数的参数(例如,min()max()sorted() 在给定一个集合列表作为输入时会产生未定义的结果)。

    集合的比较强制执行其元素的自反性。

  • 大多数其他内置类型都没有实现比较方法,因此它们继承了默认的比较行为。

自定义比较行为的用户定义类应尽可能遵循一些一致性规则。

  • 相等比较应该是自反的。换句话说,相同的对象应该比较相等。

    x is y 意味着 x == y

  • 比较应该是对称的。换句话说,以下表达式应该具有相同的结果

    x == yy == x

    x != yy != x

    x < yy > x

    x <= yy >= x

  • 比较应该是传递的。以下(非穷举)示例说明了这一点

    x > y and y > z 意味着 x > z

    x < y and y <= z 意味着 x < z

  • 逆比较应该导致布尔否定。换句话说,以下表达式应该具有相同的结果

    x == y 以及 not x != y

    x < y 以及 not x >= y(对于全序)

    x > y 以及 not x <= y(对于全序)

    最后两个表达式适用于全序集合(例如序列,但不适用于集合或映射)。另请参阅 total_ordering() 装饰器。

  • hash() 结果应与相等性一致。相等的物件应该具有相同的哈希值,或者被标记为不可哈希。

Python 不强制执行这些一致性规则。事实上,非数字值就是不遵循这些规则的例子。

6.10.2. 成员测试操作

运算符 innot in 用于测试成员资格。 x in s 如果 xs 的成员,则计算结果为 True,否则为 Falsex not in s 返回 x in s 的否定。所有内置序列和集合类型都支持此功能,以及字典,对于字典,in 测试字典是否具有给定的键。对于容器类型,如列表、元组、集合、冻结集合、字典或 collections.deque,表达式 x in y 等效于 any(x is e or x == e for e in y)

对于字符串和字节类型,x in y 当且仅当 xy 的子字符串时为 True。等效测试是 y.find(x) != -1。空字符串始终被认为是任何其他字符串的子字符串,因此 "" in "abc" 将返回 True

对于定义了 __contains__() 方法的用户定义类,x in y 如果 y.__contains__(x) 返回真值,则返回 True,否则返回 False

对于未定义 __contains__() 但定义了 __iter__() 的用户自定义类,x in y 当在迭代 y 时产生一些值 z,对于该值表达式 x is z or x == z 为真时,结果为 True。如果在迭代期间引发异常,则相当于 in 抛出了该异常。

最后,尝试使用旧式迭代协议:如果一个类定义了 __getitem__()x in y 当且仅当存在一个非负整数索引 i,使得 x is y[i] or x == y[i],并且没有更低的整数索引引发 IndexError 异常时,结果为 True。(如果引发了任何其他异常,则相当于 in 抛出了该异常)。

运算符 not in 被定义为具有与 in 相反的真值。

6.10.3. 身份比较

运算符 isis not 用于测试对象的标识:x is y 当且仅当 xy 是同一个对象时为真。对象的标识使用 id() 函数确定。 x is not y 返回相反的真值。 [4]

6.11. 布尔运算

or_test  ::=  and_test | or_test "or" and_test
and_test ::=  not_test | and_test "and" not_test
not_test ::=  comparison | "not" not_test

在布尔运算的上下文中,以及当表达式被控制流语句使用时,以下值被解释为假:FalseNone、所有类型的数值零,以及空字符串和容器(包括字符串、元组、列表、字典、集合和冻结集合)。所有其他值都被解释为真。用户定义的对象可以通过提供 __bool__() 方法来自定义它们的真值。

运算符 not 当其参数为假时返回 True,否则返回 False

表达式 x and y 首先计算 x;如果 x 为假,则返回其值;否则,计算 y 并返回结果值。

表达式 x or y 首先计算 x;如果 x 为真,则返回其值;否则,计算 y 并返回结果值。

请注意,andor 不会将它们返回的值和类型限制为 FalseTrue,而是返回最后一个被评估的参数。这在某些情况下很有用,例如,如果 s 是一个字符串,如果它为空,则应该用默认值替换它,表达式 s or 'foo' 将产生所需的值。因为 not 必须创建一个新值,所以它返回一个布尔值,而不管其参数的类型是什么(例如,not 'foo' 生成 False 而不是 ''。)

6.12. 赋值表达式

assignment_expression ::=  [identifier ":="] expression

赋值表达式(有时也称为“命名表达式”或“walrus”)将一个 expression 赋值给一个 identifier,同时还返回 expression 的值。

一个常见的用例是在处理匹配的正则表达式时

if matching := pattern.search(data):
    do_something(matching)

或者,在分块处理文件流时

while chunk := file.read(9000):
    process(chunk)

赋值表达式在用作表达式语句时以及用作切片、条件、lambda、关键字参数和推导式 if 表达式以及在 assertwithassignment 语句中的子表达式时,必须用括号括起来。在所有其他可以使用它们的地方,括号不是必需的,包括在 ifwhile 语句中。

在版本 3.8 中添加: 有关赋值表达式的更多详细信息,请参见 PEP 572

6.13. 条件表达式

conditional_expression ::=  or_test ["if" or_test "else" expression]
expression             ::=  conditional_expression | lambda_expr

条件表达式(有时称为“三元运算符”)具有所有 Python 操作的最低优先级。

表达式 x if C else y 首先评估条件,C 而不是 x。如果 C 为真,则评估 x 并返回其值;否则,评估 y 并返回其值。

有关条件表达式的更多详细信息,请参见 PEP 308

6.14. Lambda 表达式

lambda_expr ::=  "lambda" [parameter_list] ":" expression

Lambda 表达式(有时称为 lambda 形式)用于创建匿名函数。表达式 lambda parameters: expression 生成一个函数对象。未命名的对象的行为类似于使用以下方法定义的函数对象

def <lambda>(parameters):
    return expression

有关参数列表的语法,请参见第 函数定义 节。请注意,使用 lambda 表达式创建的函数不能包含语句或注释。

6.15. 表达式列表

expression_list    ::=  expression ("," expression)* [","]
starred_list       ::=  starred_item ("," starred_item)* [","]
starred_expression ::=  expression | (starred_item ",")* [starred_item]
starred_item       ::=  assignment_expression | "*" or_expr

除了作为列表或集合显示的一部分之外,包含至少一个逗号的表达式列表将生成一个元组。元组的长度是列表中表达式的数量。表达式从左到右进行评估。

星号 * 表示 可迭代解包。其操作数必须是 可迭代对象。可迭代对象被扩展成一系列项目,这些项目包含在新的元组、列表或集合中,在解包的位置。

在版本 3.5 中添加: 表达式列表中的可迭代解包,最初由 PEP 448 提出。

尾随逗号仅在创建单项元组时才需要,例如 1,;在所有其他情况下都是可选的。没有尾随逗号的单个表达式不会创建元组,而是生成该表达式的值。(要创建空元组,请使用一对空括号:()。)

6.16. 表达式求值顺序

Python 从左到右对表达式进行求值。注意,在求值赋值语句时,右边的表达式会在左边表达式之前进行求值。

在以下代码行中,表达式将按照其后缀的算术顺序进行求值

expr1, expr2, expr3, expr4
(expr1, expr2, expr3, expr4)
{expr1: expr2, expr3: expr4}
expr1 + expr2 * (expr3 - expr4)
expr1(expr2, expr3, *expr4, **expr5)
expr3, expr4 = expr1, expr2

6.17. 运算符优先级

下表总结了 Python 中运算符的优先级,从最高优先级(最紧密结合)到最低优先级(最松散结合)。同一框中的运算符具有相同的优先级。除非语法明确给出,否则运算符为二元运算符。同一框中的运算符从左到右分组(指数运算和条件表达式除外,它们从右到左分组)。

请注意,比较、成员测试和身份测试的优先级相同,并且具有如 比较 部分所述的从左到右的链式特征。

运算符

描述

(expressions...),

[expressions...], {key: value...}, {expressions...}

绑定或括号表达式、列表显示、字典显示、集合显示

x[index], x[index:index], x(arguments...), x.attribute

下标、切片、调用、属性引用

await x

等待表达式

**

指数运算 [5]

+x, -x, ~x

正号、负号、按位取反

*, @, /, //, %

乘法、矩阵乘法、除法、地板除、取余 [6]

+, -

加法和减法

<<, >>

移位

&

按位与

^

按位异或

|

按位或

in, not in, is, is not, <, <=, >, >=, !=, ==

比较,包括成员测试和身份测试

not x

布尔非

and

布尔与

or

布尔或

ifelse

条件表达式

lambda

Lambda 表达式

:=

赋值表达式

脚注