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 异常。

6.2.1.1. 私有名称改写

当一个在类定义中出现的标识符以两个或多个下划线字符开头且不以两个或多个下划线结尾时,它被认为是该类的 私有名称

参见

请参阅 类规范

更准确地说,私有名称在为其生成代码之前会被转换为更长的形式。如果转换后的名称长度超过 255 个字符,可能会发生由具体实现定义的截断。

转换独立于使用该标识符的句法上下文,但只有以下私有标识符会被改写

  • 任何用作被赋值、读取的变量名,或任何被访问的属性名。

    然而,嵌套函数、类和类型别名的 __name__ 属性不会被改写。

  • 导入的模块名,例如,import __spam 中的 __spam。如果该模块是包的一部分(即其名称包含点),则该名称会被改写,例如,import __foo.bar 中的 __foo 不会被改写。

  • 导入的成员名,例如,from spam import __f 中的 __f

转换规则定义如下

  • 类名(去除前导下划线并插入一个前导下划线)被插入到标识符前面。例如,在名为 Foo_Foo__Foo 的类中出现的标识符 __spam 会被转换为 _Foo__spam

  • 如果类名仅由下划线组成,则不进行转换。例如,在名为 ___ 的类中出现的标识符 __spam 会保持原样。

6.2.2. 字面值

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

literal: strings | NUMBER

对一个字面值求值会产生一个具有给定值的给定类型的对象(字符串、字节串、整数、浮点数、复数)。在浮点数和虚数(复数)字面值的情况下,该值可能是近似值。详情请参阅 字面值 一节。关于 strings 的详情请参阅 字符串字面值的拼接 一节。

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

6.2.2.1. 字符串字面值的拼接

多个相邻的字符串或字节串字面值(由空白符分隔),可能使用不同的引号约定,是允许的,它们的含义与它们的拼接相同:

>>> "hello" 'world'
"helloworld"

形式上:

strings: ( STRING | fstring)+ | tstring+

此特性在句法层面定义,因此仅适用于字面值。要在运行时拼接字符串表达式,可以使用 '+' 运算符:

>>> greeting = "Hello"
>>> space = " "
>>> name = "Blaise"
>>> print(greeting + space + name)   # not: print(greeting space name)
Hello Blaise

字面值拼接可以自由混合使用原始字符串、三引号字符串和格式化字符串字面值。例如:

>>> "Hello" r', ' f"{name}!"
"Hello, Blaise!"

此特性可以用来减少所需的反斜杠数量,方便地将长字符串跨多行分割,甚至为字符串的某些部分添加注释。例如:

re.compile("[A-Za-z_]"       # letter or underscore
           "[A-Za-z0-9_]*"   # letter, digit or underscore
          )

但是,字节串字面值只能与其他字节串字面值组合,不能与任何类型的字符串字面值组合。同样,模板字符串字面值也只能与其他模板字符串字面值组合:

>>> t"Hello" t"{name}!"
Template(strings=('Hello', '!'), interpolations=(...))

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 表达式或其他异步推导式(除了最左边 for 子句中的可迭代表达式之外),则它被称为 异步推导式。异步推导式可能会暂停其所在的协程函数的执行。另请参阅 PEP 530

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

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

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

6.2.5. 列表显示

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

list_display: "[" [flexible_expression_list | comprehension] "]"

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

6.2.6. 集合显示

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

set_display: "{" (flexible_expression_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" yield_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 表达式,在那里再次暂停,将 yield_list 的值返回给生成器的调用者,如果 yield_list 被省略,则返回 None。所谓的暂停,是指所有局部状态都被保留,包括局部变量的当前绑定、指令指针、内部求值栈以及任何异常处理的状态。当通过调用生成器的一个方法恢复执行时,函数可以像 yield 表达式只是另一个外部调用一样继续执行。恢复后 yield 表达式的值取决于恢复执行的方法。如果使用 __next__()(通常通过 for 循环或内置函数 next()),则结果是 None。否则,如果使用 send(),则结果将是传入该方法的值。

所有这些使得生成器函数与协程非常相似;它们多次 yield,它们有多个入口点,并且它们的执行可以被暂停。唯一的区别是,生成器函数无法控制它 yield 之后执行应该在哪里继续;控制权总是转移给生成器的调用者。

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 表达式,在那里生成器再次被暂停,并且 yield_list 的值被返回给 __next__() 的调用者。如果生成器在没有 yield 另一个值的情况下退出,则会引发一个 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 异常(等同于调用 throw(GeneratorExit))。该异常由生成器暂停的 yield 表达式引发。如果生成器函数捕获了该异常并返回一个值,则该值会从 close() 返回。如果生成器函数已经关闭,或者引发了 GeneratorExit(通过不捕获该异常),close() 会返回 None。如果生成器产生了一个值,则会引发一个 RuntimeError。如果生成器引发了任何其他异常,它会传播给调用者。如果生成器由于异常或正常退出已经退出,close() 会返回 None 并且没有其他效果。

在 3.13 版更改: 如果一个生成器在关闭时返回一个值,该值会由 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 表达式,在那里再次被暂停,并将 yield_list 的值返回给等待的协程。与生成器一样,暂停意味着所有局部状态都被保留,包括局部变量的当前绑定、指令指针、内部求值栈以及任何异常处理的状态。当通过等待异步生成器方法返回的下一个对象来恢复执行时,函数可以像 yield 表达式只是另一个外部调用一样继续执行。恢复后 yield 表达式的值取决于恢复执行的方法。如果使用 __anext__(),则结果是 None。否则,如果使用 asend(),则结果将是传入该方法的值。

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

在异步生成器函数中,yield 表达式允许在 try 结构的任何地方使用。但是,如果一个异步生成器在最终确定之前(通过达到零引用计数或被垃圾回收)没有被恢复,那么 try 结构内的 yield 表达式可能会导致待处理的 finally 子句执行失败。在这种情况下,运行异步生成器的事件循环或调度器有责任调用异步生成器-迭代器的 aclose() 方法并运行产生的协程对象,从而允许任何待处理的 finally 子句执行。

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

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

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

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

async agen.__anext__()

返回一个可等待对象,当它运行时,会开始执行异步生成器或在上次执行的 yield 表达式处恢复它。当一个异步生成器函数通过 __anext__() 方法恢复时,当前的 yield 表达式在返回的可等待对象中总是求值为 None,当它运行时将继续到下一个 yield 表达式。yield 表达式的 yield_list 的值是完成的协程引发的 StopIteration 异常的值。如果异步生成器在没有产生另一个值的情况下退出,该可等待对象会引发一个 StopAsyncIteration 异常,表示异步迭代已完成。

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

async agen.asend(value)

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

async agen.athrow(value)
async agen.athrow(type[, value[, traceback]])

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

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

async 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. 订阅

容器类 实例的订阅通常会从容器中选择一个元素。对 泛型类 的订阅通常会返回一个 泛型别名 对象。

subscription: primary "[" flexible_expression_list "]"

当一个对象被订阅时,解释器将对原语和表达式列表进行求值。

原语必须求值为一个支持订阅的对象。一个对象可以通过定义 __getitem__()__class_getitem__() 中的一个或两个来支持订阅。当原语被订阅时,表达式列表的求值结果将被传递给这些方法之一。关于何时调用 __class_getitem__ 而不是 __getitem__ 的更多细节,请参见 __class_getitem__ 与 __getitem__

如果表达式列表包含至少一个逗号,或者任何表达式带星号,表达式列表将求值为一个包含表达式列表项的 tuple。否则,表达式列表将求值为该列表唯一成员的值。

在 3.11 版更改: 表达式列表中的表达式可以带星号。请参见 PEP 646

对于内置对象,有两种类型的对象通过 __getitem__() 支持订阅:

  1. 映射。如果原语是一个 映射,表达式列表必须求值为一个对象,其值是该映射的键之一,订阅会选择映射中与该键对应的值。内置映射类的一个例子是 dict 类。

  2. 序列。如果原语是一个 序列,表达式列表必须求值为一个 int 或一个 slice(如下一节所讨论)。内置序列类的例子包括 strlisttuple 类。

正式语法没有为 序列 中的负数索引做出特殊规定。然而,内置序列都提供了一个 __getitem__() 方法,通过将序列的长度加到索引上来解释负数索引,因此,例如 x[-1] 选择 x 的最后一项。结果值必须是一个小于序列中项数的非负整数,订阅会选择索引为该值的项(从零开始计数)。由于对负数索引和切片的支持发生在对象的 __getitem__() 方法中,重写此方法的子类将需要明确添加该支持。

string 是一种特殊的序列,其项是 *字符*。字符不是一个独立的数据类型,而是一个长度正好为一的字符串。

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 个定位参数 *x1*, *x2*, *y1*, ..., *yM*, *x3*, *x4* 的调用。

这样做的一个后果是,虽然 *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 语句时,将指定函数调用的返回值。如果代码块执行到末尾而没有执行 return 语句,则返回值为 None

内置函数或方法

结果由解释器决定;内置函数和方法的描述请见 内置函数

类对象

返回该类的一个新实例。

类实例方法

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

类实例

该类必须定义 __call__() 方法;其效果等同于调用了该方法。

6.4. Await 表达式

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

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__() 方法进行自定义。

在 3.14 版本发生变更: 如果只有一个操作数是复数,则另一个操作数将被转换为浮点数。

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

此操作可通过特殊的 __matmul__()__rmatmul__() 方法进行自定义。

在 3.5 版本加入。

/ (除法) 和 // (整除) 运算符返回其参数的商。数值参数会先被转换为通用类型。整数除法会产生一个浮点数,而整数整除则会得到一个整数;其结果是对数学除法的结果应用 'floor' (向下取整) 函数。除以零会引发 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 风格的字符串格式化 一节中有详细描述。

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

整除运算符、取模运算符和 divmod() 函数未对复数定义。如果需要,可使用 abs() 函数将其转换为浮点数。

+ (加法) 运算符得出其参数的和。参数要么都必须是数字,要么都必须是相同类型的序列。在前一种情况下,数字会先被转换为通用的实数类型,然后相加。在后一种情况下,序列会被拼接。

此操作可通过特殊的 __add__()__radd__() 方法进行自定义。

在 3.14 版本发生变更: 如果只有一个操作数是复数,则另一个操作数将被转换为浮点数。

- (减法) 运算符得出其参数的差。数值参数会先被转换为通用的实数类型。

此操作可通过特殊的 __sub__()__rsub__() 方法进行自定义。

在 3.14 版本发生变更: 如果只有一个操作数是复数,则另一个操作数将被转换为浮点数。

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__()__rand__() 特殊方法的自定义对象。

^ 运算符返回其参数的按位异或(XOR)结果,参数必须是整数,或者其中之一是重写了 __xor__()__rxor__() 特殊方法的自定义对象。

| 运算符返回其参数的按位(或)或结果,参数必须是整数,或者其中之一是重写了 __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 完全不会被求值)。

形式上,如果 a, b, c, ..., y, z 是表达式,op1, op2, ..., 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 并不意味着 ac 之间有任何类型的比较,因此,例如 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 的实例)只能在各自的类型内部进行比较,但有一个限制,即 range 不支持顺序比较。这些类型之间的相等性比较结果为不相等,而跨类型的顺序比较会引发 TypeError

    序列使用对应元素的比较进行字典序比较。内置容器通常假定相同的对象彼此相等。这使它们可以跳过对相同对象的相等性测试,以提高性能并维护其内部不变量。

    内置集合之间的字典序比较工作方式如下:

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

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

  • 映射(dict 的实例)当且仅当它们具有相等的 (键, 值) 对时才比较为相等。键和值的相等性比较强制执行自反性。

    顺序比较(<><=>=)会引发 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 == ynot x != y

    x < ynot x >= y (对于全序)

    x > ynot x <= y (对于全序)

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

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

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

6.10.2. 成员检测运算

运算符 innot in 用于检测成员关系。x in sxs 的一个成员时结果为 True,否则为 Falsex not in s 返回 x in s 的逻辑非结果。所有内置序列和集合类型都支持此运算,字典也支持,对于字典,in 检测字典是否拥有给定的键。对于容器类型如 list、tuple、set、frozenset、dict 或 collections.deque,表达式 x in y 等价于 any(x is e or x == e for e in y)

对于 string 和 bytes 类型,x in yTrue 当且仅当 xy 的一个子串。一个等价的测试是 y.find(x) != -1。空字符串总是被认为是任何其他字符串的子串,因此 "" in "abc" 将返回 True

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

对于未定义 __contains__() 但定义了 __iter__() 的用户自定义类,x in yTrue,如果能在迭代 y 的过程中找到某个值 z 使得表达式 x is z or x == z 为真。如果在迭代过程中引发了异常,就好像是 in 引发了那个异常。

最后,会尝试旧式的迭代协议:如果一个类定义了 __getitem__()x in yTrue 当且仅当存在一个非负整数索引 i 使得 x is y[i] or x == y[i],并且没有更小的整数索引引发 IndexError 异常。(如果引发了任何其他异常,就好像是 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

赋值表达式(有时也称为“命名表达式”或“海象运算符”)将一个 表达式 赋给一个 标识符,同时还返回该 表达式 的值。

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

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

条件表达式(有时称为“三元运算符”)是 if-else 语句的替代方案。由于它是一个表达式,它会返回一个值,并且可以作为子表达式出现。

表达式 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. 表达式列表

starred_expression:       "*" or_expr | expression
flexible_expression:      assignment_expression | starred_expression
flexible_expression_list: flexible_expression ("," flexible_expression)* [","]
starred_expression_list:  starred_expression ("," starred_expression)* [","]
expression_list:          expression ("," expression)* [","]
yield_list:               expression_list | starred_expression "," [starred_expression_list]

除非作为列表或集合显示的一部分,否则包含至少一个逗号的表达式列表会产生一个元组。元组的长度是列表中表达式的数量。表达式从左到右求值。

一个星号 * 表示可迭代对象解包。其操作数必须是一个可迭代对象。该可迭代对象被展开为一个元素序列,这些元素在解包的位置被包含到新的元组、列表或集合中。

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

在 3.11 版本加入: 表达式列表中的任何项都可以加星号。参见 PEP 646

仅在创建单元素元组时需要一个末尾的逗号,例如 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 中的运算符优先级,从最高优先级(最紧密绑定)到最低优先级(最松散绑定)。同一框中的运算符具有相同的优先级。除非明确给出语法,否则运算符是二元的。同一框中的运算符从左到右分组(除了乘方和条件表达式,它们从右到左分组)。

请注意,比较、成员检测和标识检测都具有相同的优先级,并具有从左到右的链式特性,如比较运算一节所述。

运算符

描述

(表达式...),

[表达式...], {键: 值...}, {表达式...}

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

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

订阅、切片、调用、属性引用

await x

Await 表达式

**

乘方 [5]

+x, -x, ~x

正、负、按位非

*, @, /, //, %

乘法、矩阵乘法、除法、整除、取余 [6]

+, -

加法和减法

<<, >>

移位

&

按位与

^

按位异或

|

按位或

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

比较,包括成员检测和标识检测

not x

布尔非

布尔与

布尔或

ifelse

条件表达式

lambda

Lambda 表达式

:=

赋值表达式

脚注