Python原子

2023-11-20 78

Python编程语言中,“原子”指表达式的最基本构成元素。 最简单的原子是标识符和字面值。 以圆括号、方括号或花括号包括的形式在语法上也被归类为原子, 但通常情况下,表达式是由原子之间通过运算符进行的计算构成的。

一、原子句法

原子的句法为:

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

二、标识符

作为原子出现的标识符叫做名称。 当名称被绑定到一个对象时,对该原子求值将返回相应对象。 当名称未被绑定时,尝试对其求值将引发 NameError 异常。

私有名称转换:当以文本形式出现在类定义中的一个标识符以两个或更多下划线开头并且不以两个或更多下划线结尾,它会被视为该类的 私有名称。 私有名称会在为其生成代码之前被转换为一种更长的形式。 转换时会插入类名,移除打头的下划线再在名称前增加一个下划线。

例如,出现在一个名为 Ham 的类中的标识符 __spam 会被转换为 _Ham__spam。 这种转换独立于标识符所使用的相关句法。 如果转换后的名称太长(超过 255 个字符),可能发生由具体实现定义的截断。 如果类名仅由下划线组成,则不会进行转换。

三、字面值

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

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

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

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

四、带圆括号的形式

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

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

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

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

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

五、列表/集合/字典的显示

为了构建列表、集合或字典,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 子句和零个或多个 for 或 if 子句。在这个结构中,新容器的元素是通过将每个 for 或 if 子句视为一个代码块并按从左到右的顺序嵌套来生成的。

推导式在另一个隐式嵌套的作用域内执行,以确保赋给目标列表的名称不会泄露到外层作用域。最左边的 for 子句中的可迭代对象表达式会直接在外层作用域中被求值,然后作为参数传递给隐式嵌套的作用域。后续的 for 子句以及最左侧 for 子句中的任何筛选条件不能在外层作用域中被求值,因为它们可能依赖于从最左侧可迭代对象中获得的值。

为了确保推导式得出的结果总是一个类型正确的容器,在隐式嵌套作用域内禁止使用 yield 和 yield from 表达式。这是因为这些表达式可能会导致生成器函数的行为与预期不符,从而影响推导式的计算结果。

从 Python 3.6 开始,可以在 async def 函数中使用 async for 子句来异步迭代一个 asynchronous iterator。在 async def 函数中的推导式可以通过在表达式后加上 for 或 async for 子句来构建,并可以包含额外的 for 或 async for 子句以及 await 表达式。如果推导式中包含 async for 子句、await 表达式或其他形式的异步推导式,那么它被称为异步推导式。异步推导式可以让协程函数暂停执行。

六、列表显示

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

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

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

七、集合显示

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

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

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

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

八、字典显示

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

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

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

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

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

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

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

九、生成器表达式

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

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

生成器表达式会产生一个新的生成器对象。其句法与推导式相同,区别在于它是用圆括号而不是用方括号或花括号括起来的。

在生成器表达式中使用的变量会在为生成器对象调用 __next__() 方法的时候以惰性方式被求值(即与普通生成器相同的方式)。 但是,最左侧 for 子句内的可迭代对象是会被立即求值的,因此它所造成的错误会在生成器表达式被定义时被检测到,而不是在获取第一个值时才出错。 后续的 for 子句以及最左侧 for 子句内的任何筛选条件无法在外层作用域内被求值,因为它们可能会依赖于从最左侧可迭代对象获取的值。 例如: (x*y for x in range(10) for y in range(x, x+10)).

为了避免干扰到生成器表达式本身的预期操作,禁止在隐式定义的生成器中使用 yield 和 yield from 表达式。

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

十、yield表达式

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

yield 表达式在定义 generator 函数或 asynchronous generator 函数时才会用到因此只能在函数定义的内部使用。 在一个函数体内使用 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 多次,它们具有多个入口点,并且它们的执行可以被挂起。唯一的区别是生成器函数不能控制在它在 yield 后交给哪里继续执行;控制权总是转移到生成器的调用者。
  • 在 try 结构中的任何位置都允许yield表达式。如果生成器在(因为引用计数到零或是因为被垃圾回收)销毁之前没有恢复执行,将调用生成器-迭代器的 close() 方法. close 方法允许任何挂起的 finally 子句执行。
  • 当使用 yield from <expr> 时,所提供的表达式必须是一个可迭代对象。 迭代该可迭代对象所产生的值会被直接传递给当前生成器方法的调用者。 任何通过 send() 传入的值以及任何通过 throw() 传入的异常如果有适当的方法则会被传给下层迭代器。 如果不是这种情况,那么 send() 将引发 AttributeError 或 TypeError,而 throw() 将立即引发所转入的异常。

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

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

在 3.3 版更改:

添加 yield from <expr> 以委托控制流给一个子迭代器。

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 (由于未捕获该异常) 则关闭并返回其调用者。 如果生成器产生了一个值,关闭会引发 RuntimeError。 如果生成器引发任何其他异常,它会被传播给调用者。 如果生成器已经由于异常或正常退出则 close() 不会做任何事。

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.

3、异步生成器函数

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

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

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

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

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

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

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

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(value):coroutine agen.athrow(type[, value[, traceback]])
    返回一个可等待对象,它会在异步生成器暂停的位置引发 type 类型的异常,并返回该生成器函数所产生的下一个值,其值为所引发的 StopIteration 异常。 如果异步生成器没有产生下一个值就退出,则将由该可等待对象引发 StopAsyncIteration 异步。 如果生成器函数没有捕获传入的异常,或引发了另一个异常,则当可等待对象运行时该异常会被传播给可等待对象的调用者。

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

  • coroutine agen.aclose():返回一个可等待对象,它会在运行时向异步生成器函数暂停的位置抛入一个 GeneratorExit。 如果该异步生成器函数正常退出、关闭或引发 GeneratorExit (由于未捕获该异常) 则返回的可等待对象将引发 StopIteration 异常。 后续调用异步生成器所返回的任何其他可等待对象将引发 StopAsyncIteration 异常。 如果异步生成器产生了一个值,该可等待对象会引发 RuntimeError。 如果异步生成器引发任何其他异常,它会被传播给可等待对象的调用者。 如果异步生成器已经由于异常或正常退出则后续调用 aclose() 将返回一个不会做任何事的可等待对象。
  • 广告合作

  • QQ群号:707632017

温馨提示:
1、本网站发布的内容(图片、视频和文字)以原创、转载和分享网络内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。邮箱:2942802716#qq.com(#改为@)。 2、本站原创内容未经允许不得转裁,转载请注明出处“站长百科”和原文地址。
Python原子
下一篇: Python原型