6.2. 原子

“原子”指表达式的最基本构成元素。 最简单的原子是标识符和字面值。 以圆括号、方括号或花括号包括的形式在语法上也被归类为原子。 原子的句法为:

  1. atom ::= identifier | literal | enclosure
  2. enclosure ::= parenth_form | list_display | dict_display | set_display
  3. | generator_expression | yield_atom

6.2.1. 标识符(名称)

作为原子出现的标识符叫做名称。 请参看 标识符和关键字 一节了解其词法定义,以及 命名与绑定 获取有关命名与绑定的文档。

当名称被绑定到一个对象时,对该原子求值将返回相应对象。 当名称未被绑定时,尝试对其求值将引发 NameError 异常。

私有名称转换: 当以文本形式出现在类定义中的一个标识符以两个或更多下划线开头并且不以两个或更多下划线结尾,它会被视为该类的 私有名称。 私有名称会在为其生成代码之前被转换为一种更长的形式。 转换时会插入类名,移除打头的下划线再在名称前增加一个下划线。 例如,出现在一个名为 Ham 的类中的标识符 spam 会被转换为 _Hamspam。 这种转换独立于标识符所使用的相关句法。 如果转换后的名称太长(超过 255 个字符),可能发生由具体实现定义的截断。 如果类名仅由下划线组成,则不会进行转换。

6.2.2. 字面值

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

  1. literal ::= stringliteral | bytesliteral
  2. | integer | floatnumber | imagnumber

对字面值求值将返回一个该值所对应类型的对象(字符串、字节串、整数、浮点数、复数)。 对于浮点数和虚数(复数)的情况,该值可能为近似值。 详情参见 字面值

所有字面值都对应与不可变数据类型,因此对象标识的重要性不如其实际值。 多次对具有相同值的字面值求值(不论是发生在程序文本的相同位置还是不同位置)可能得到相同对象或是具有相同值的不同对象。

6.2.3. 带圆括号的形式

带圆括号的形式是包含在圆括号中的可选表达式列表。

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

带圆括号的表达式列表将返回该表达式列表所产生的任何东西:如果该列表包含至少一个逗号,它会产生一个元组;否则,它会产生该表达式列表所对应的单一表达式。

一对内容为空的圆括号将产生一个空的元组对象。 由于元组是不可变对象,因此适用与字面值相同的规则(即两次出现的空元组产生的对象可能相同也可能不同)。

请注意元组并不是由圆括号构建,实际起作用的是逗号操作符。 例外情况是空元组,这时圆括号 才是 必须的 —- 允许在表达式中使用不带圆括号的 "空" 会导致歧义,并会造成常见输入错误无法被捕获。

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

为了构建列表、集合或字典,Python 提供了名为“显示”的特殊句法,每个类型各有两种形式:

  • 第一种是显式地列出容器内容

  • 第二种是通过一组循环和筛选指令计算出来,称为 推导式

推导式的常用句法元素为:

  1. comprehension ::= expression comp_for
  2. comp_for ::= ["async"] "for" target_list "in" or_test [comp_iter]
  3. comp_iter ::= comp_for | comp_if
  4. comp_if ::= "if" expression_nocond [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.7 中,这样的表达式会在编译时引发 DeprecationWarning,在 Python 3.8+ 中它们将引发 SyntaxError)。

从 Python 3.6 开始,在 async def 函数中可以使用 async for 子句来迭代 asynchronous iterator。 在 async def 函数中构建推导式可以通过在打头的表达式后加上 forasync for 子句,也可能包含额外的 forasync for 子句,还可能使用 await 表达式。 如果一个推导式包含 async for 子句或者 await 表达式,则被称为 异步推导式。 异步推导式可以暂停执行它所在的协程函数。 另请参阅 PEP 530

3.6 新版功能: 引入了异步推导式。

3.7 版后已移除: yield and yield from 在隐式嵌套的作用域内已弃用。

6.2.5. 列表显示

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

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

列表显示会产生一个新的列表对象,其内容通过一系列表达式或一个推导式来指定。 当提供由逗号分隔的一系列表达式时,其元素会从左至右被求值并按此顺序放入列表对象。 当提供一个推导式时,列表会根据推导式所产生的结果元素进行构建。

6.2.6. 集合显示

集合显示是用花括号标明的,与字典显示的区别在于没有冒号分隔的键和值:

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

集合显示会产生一个新的可变集合对象,其内容通过一系列表达式或一个推导式来指定。 当提供由逗号分隔的一系列表达式时,其元素会从左至右被求值并加入到集合对象。 当提供一个推导式时,集合会根据推导式所产生的结果元素进行构建。

空集合不能用 {} 来构建;该字面值所构建的是一个空字典。

6.2.7. 字典显示

字典显示是一个用花括号括起来的可能为空的键/数据对系列:

  1. dict_display ::= "{" [key_datum_list | dict_comprehension] "}"
  2. key_datum_list ::= key_datum ("," key_datum)* [","]
  3. key_datum ::= expression ":" expression | "**" or_expr
  4. dict_comprehension ::= expression ":" expression comp_for

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

如果给出一个由逗号分隔的键/数据对序列,它们会从左至右被求值以定义字典的条目:每个键对象会被用作在字典中存放相应数据的键。 这意味着你可以在键/数据对序列中多次指定相同的键,最终字典的值将由最后一次给出的键决定。

双星号 ** 表示 字典拆包。 它的操作数必须是一个 mapping。 每个映射项被会加入新的字典。 后续的值会替代先前的键/数据对和先前的字典拆包所设置的值。

3.5 新版功能: 拆包到字典显示,最初由 PEP 448 提出。

字典推导式与列表和集合推导式有所不同,它需要以冒号分隔的两个表达式,后面带上标准的 "for" 和 "if" 子句。 当推导式被执行时,作为结果的键和值元素会按它们的产生顺序被加入新的字典。

对键取值类型的限制已列在之前的 标准类型层级结构 一节中。 (总的说来,键的类型应该为 hashable,这就把所有可变对象都排除在外。) 重复键之间的冲突不会被检测;指定键所保存的最后一个数据 (即在显示中排最右边的文本) 为最终有效数据。

6.2.8. 生成器表达式

生成器表达式是用圆括号括起来的紧凑形式生成器标注。

  1. 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 表达式禁止在隐式定义的生成器中使用 (在 Python 3.7 中,这种表达式会在编译时引发 DeprecationWarning,在 Python 3.8+ 中它们将引发 SyntaxError)。

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

3.6 新版功能: 引入了异步生成器表达式。

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

3.7 版后已移除: yield and yield from 在隐式嵌套的作用域内已弃用。

6.2.9. yield 表达式

  1. yield_atom ::= "(" yield_expression ")"
  2. yield_expression ::= "yield" [expression_list | "from" expression]

yield 表达式在定义 generator 函数或是 asynchronous generator 的时候才会用到。 因此只能在函数定义的内部使用yield表达式。 在一个函数体内使用 yield 表达式会使这个函数变成一个生成器,并且在一个 async def 定义的函数体内使用 yield 表达式会让协程函数变成异步的生成器。 比如说:

  1. def gen(): # defines a generator function
  2. yield 123
  3.  
  4. async def agen(): # defines an asynchronous generator function
  5. yield 123

由于它们会对外层作用域造成附带影响,yield 表达式不被允许作为用于实现推导式和生成器表达式的隐式定义作用域的一部分 (在 Python 3.7 中,此类表达式会在编译时引发 DeprecationWarning,在 Python 3.8+ 中它们将引发 SyntaxError)。

3.7 版后已移除: yield 表达式在用于实现推导式和生成器表达式的隐式嵌套作用域中已弃用。

下面是对生成器函数的描述,异步生成器函数会在 异步生成器函数 一节中单独介绍。

当一个生成器函数被调用的时候,它返回一个迭代器,称为生成器。然后这个生成器来控制生成器函数的执行。当这个生成器的某一个方法被调用的时候,生成器函数开始执行。这时会一直执行到第一个 yield 表达式,在此执行再次被挂起,给生成器的调用者返回 expression_list 的值。挂起后,我们说所有局部状态都被保留下来,包括局部变量的当前绑定,指令指针,内部求值栈和任何异常处理的状态。通过调用生成器的某一个方法,生成器函数继续执行。此时函数的运行就和 yield 表达式只是一个外部函数调用的情况完全一致。恢复后 yield 表达式的值取决于调用的哪个方法来恢复执行。 如果用的是 next() (通常通过语言内置的 for 或是 next() 来调用) 那么结果就是 None. 否则,如果用 send(), 那么结果就是传递给send方法的值。

所有这些使生成器函数与协程非常相似;它们 yield 多次,它们具有多个入口点,并且它们的执行可以被挂起。唯一的区别是生成器函数不能控制在它在 yield 后交给哪里继续执行;控制权总是转移到生成器的调用者。

try 结构中的任何位置都允许yield表达式。如果生成器在(因为引用计数到零或是因为被垃圾回收)销毁之前没有恢复执行,将调用生成器-迭代器的 close() 方法. 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(type[, value[, traceback]])
  • 在生成器暂停的位置引发 type 类型的异常,并返回该生成器函数所产生的下一个值。 如果生成器没有产生下一个值就退出,则将引发 StopIteration 异常。 如果生成器函数没有捕获传入的异常,或引发了另一个异常,则该异常会被传播给调用者。
  • generator.close()
  • 在生成器函数暂停的位置引发 GeneratorExit。 如果之后生成器函数正常退出、关闭或引发 GeneratorExit (由于未捕获该异常) 则关闭并返回其调用者。 如果生成器产生了一个值,关闭会引发 RuntimeError。 如果生成器引发任何其他异常,它会被传播给调用者。 如果生成器已经由于异常或正常退出则 close() 不会做任何事。

6.2.9.2. 例子

这里是一个简单的例子,演示了生成器和生成器函数的行为:

  1. >>> def echo(value=None):
  2. ... print("Execution starts when 'next()' is called for the first time.")
  3. ... try:
  4. ... while True:
  5. ... try:
  6. ... value = (yield value)
  7. ... except Exception as e:
  8. ... value = e
  9. ... finally:
  10. ... print("Don't forget to clean up when 'close()' is called.")
  11. ...
  12. >>> generator = echo(1)
  13. >>> print(next(generator))
  14. Execution starts when 'next()' is called for the first time.
  15. 1
  16. >>> print(next(generator))
  17. None
  18. >>> print(generator.send(2))
  19. 2
  20. >>> generator.throw(TypeError, "spam")
  21. TypeError('spam',)
  22. >>> generator.close()
  23. Don't forget to clean up when 'close()' is called.

对于 yield from 的例子, 参见“Python 有什么新变化”中的 PEP 380: 委托给子生成器的语法

6.2.9.3. 异步生成器函数

在一个使用 async def 定义的函数或方法中出现的 yield 表达式会进一步将该函数定义为一个 asynchronous generator 函数。

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

调用异步生成器的方法之一将返回 awaitable 对象,执行会在此对象被等待时启动。 到那时,执行将前往第一个 yield 表达式,在那里它会再次暂停,将 expression_list 的值返回给等待中的协程。 与生成器一样,挂起意味着局部的所有状态会被保留,包括局部变量的当前绑定、指令的指针、内部求值的堆栈以及任何异常处理的状态。 当执行在等待异步生成器的方法返回下一个对象后恢复时,该函数可以从原状态继续进行,就仿佛 yield 表达式只是另一个外部调用。 恢复执行之后 yield 表达式的值取决于恢复执行所用的方法。 如果使用 anext() 则结果为 None。 否则的话,如果使用 asend() 则结果将是传递给该方法的值。

在异步生成器函数中,yield 表达式允许出现在 try 结构的任何位置。 但是,如果一个异步生成器在其被终结(由于引用计数达到零或被作为垃圾回收)之前未被恢复,则then a yield expression within a try 结构中的 yield 表达式可能导致挂起的 finally 子句执行失败。 在此情况下,应由运行该异步生成器的事件循环或任务调度器来负责调用异步生成器-迭代器的 aclose() 方法并运行所返回的协程对象,从而允许任何挂起的 finally 子句得以执行。

为了能处理最终化,事件循环应该定义一个 终结器 函数,它接受一个异步生成器-迭代器且可能调用 aclose() 并执行协程。 这个 终结器 可能通过调用 sys.set_asyncgen_hooks() 来注册。 当首次迭代时,异步生成器-迭代器将保存已注册的 终结器 以便在最终化时调用。 有关For a reference example of a 终结器 方法的参考示例请查看 Lib/asyncio/base_events.py 中实现的 asyncio.Loop.shutdown_asyncgens

yield from <expr> 表达式如果在异步生成器函数中使用会引发语法错误。

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

这个子小节描述了异步生成器迭代器的方法,它们可被用于控制生成器函数的执行。

  • coroutine agen.anext()
  • 返回一个可等待对象,它在运行时会开始执行该异步生成器或是从上次执行的 yield 表达式位置恢复执行。 当一个异步生成器函数通过 anext() 方法恢复执行时,当前的 yield 表达式所返回的可等待对象总是取值为 None,它在运行时将继续执行到下一个 yield 表达式。 该 yield 表达式的 expression_list 的值会是完成的协程所引发的 StopIteration 异常的值。 如果异步生成器没有产生下一个值就退出,则该可等待对象将引发 StopAsyncIteration 异常,提示该异步迭代操作已完成。

此方法通常是通过 async for 循环隐式地调用。

  • coroutine agen.asend(value)
  • 返回一个可等待对象,它在运行时会恢复该异步生成器的执行。 与生成器的 send() 方法一样,此方法会“发送”一个值给异步生成器函数,其 value 参数会成为当前 yield 表达式的结果值。 asend() 方法所返回的可等待对象将返回生成器产生的下一个值,其值为所引发的 StopIteration,或者如果异步生成器没有产生下一个值就退出则引发 StopAsyncIteration。 当调用 asend() 来启动异步生成器时,它必须以 None 作为调用参数,因为这时没有可以接收值的 yield 表达式。
  • coroutine agen.athrow(type[, value[, traceback]])
  • 返回一个可等待对象,它会在异步生成器暂停的位置引发 type 类型的异常,并返回该生成器函数所产生的下一个值,其值为所引发的 StopIteration 异常。 如果异步生成器没有产生下一个值就退出,则将由该可等待对象引发 StopAsyncIteration 异步。 如果生成器函数没有捕获传入的异常,或引发了另一个异常,则当可等待对象运行时该异常会被传播给可等待对象的调用者。
  • coroutine agen.aclose()
  • 返回一个可等待对象,它会在运行时向异步生成器函数暂停的位置抛入一个 GeneratorExit。 如果该异步生成器函数正常退出、关闭或引发 GeneratorExit (由于未捕获该异常) 则返回的可等待对象将引发 StopIteration 异常。 后续调用异步生成器所返回的任何其他可等待对象将引发 StopAsyncIteration 异常。 如果异步生成器产生了一个值,该可等待对象会引发 RuntimeError。 如果异步生成器引发任何其他异常,它会被传播给可等待对象的调用者。 如果异步生成器已经由于异常或正常退出则后续调用 aclose() 将返回一个不会做任何事的可等待对象。