6. 表达式 — Python 文档
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
对文字的求值产生具有给定值的给定类型(字符串、字节、整数、浮点数、复数)的对象。 在浮点和虚(复数)文字的情况下,该值可能是近似值。 有关详细信息,请参阅 Literals 部分。
所有文字都对应于不可变数据类型,因此对象的身份不如其值重要。 对具有相同值(在程序文本中相同出现或不同出现)的文字进行多次计算可能会获得相同的对象或具有相同值的不同对象。
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
子句和零个或多个 for
或 if
子句。 在这种情况下,新容器的元素是通过将每个 for
或 if
子句视为一个块,从左到右嵌套,并评估表达式以产生每次到达最里面的块时一个元素。
但是,除了最左边的 for
子句中的可迭代表达式之外,推导式在单独的隐式嵌套作用域中执行。 这确保分配给目标列表中的名称不会“泄漏”到封闭范围中。
最左边的 for
子句中的可迭代表达式在封闭作用域中直接求值,然后作为参数传递给隐式嵌套作用域。 后续的 for
子句和最左边的 for
子句中的任何过滤条件都不能在封闭范围内计算,因为它们可能取决于从最左边的迭代中获得的值。 例如:[x*y for x in range(10) for y in range(x, x+10)]
。
为确保推导式始终生成适当类型的容器,在隐式嵌套作用域中禁止使用 yield
和 yield from
表达式。
从 Python 3.6 开始,在 async def 函数中,可以使用 async for
子句迭代 异步迭代器 。 async def
函数中的推导式可以由前导表达式后的 for
或 async for
子句组成,可能包含额外的 for
或 async for
] 子句,也可以使用 await 表达式。 如果一个理解包含 async for
子句或 await
表达式,则称为 异步理解 。 异步理解可能会暂停它出现的协程函数的执行。 另见 PEP 530。
3.6 版新功能: 引入了异步理解。
3.8 版更改:在隐式嵌套作用域中禁止 yield
和 yield from
。
6.2.5. 列表显示
列表显示是括在方括号中的一系列可能为空的表达式:
list_display ::= "[" [starred_list | comprehension] "]"
列表显示产生一个新的列表对象,其内容由表达式列表或推导式指定。 当提供逗号分隔的表达式列表时,它的元素从左到右求值并按该顺序放入列表对象中。 当提供了一个推导式时,列表是由推导式产生的元素构造的。
6.2.6. 设置显示
集合显示由大括号表示,并通过缺少分隔键和值的冒号与字典显示区分开来:
set_display ::= "{" (starred_list | comprehension) "}"
集合显示产生一个新的可变集合对象,其内容由表达式序列或推导式指定。 当提供一个逗号分隔的表达式列表时,它的元素从左到右求值并添加到集合对象中。 当提供了一个推导式时,集合是由推导式产生的元素构造的。
不能用 {}
构造空集; 这个字面量构造了一个空字典。
6.2.7. 字典显示
字典显示是用花括号括起来的一系列可能为空的键/数据对:
dict_display ::= "{" [key_datum_list | dict_comprehension] "}" key_datum_list ::= key_datum ("," key_datum)* [","] key_datum ::= expression ":" expression | "**" or_expr dict_comprehension ::= expression ":" expression comp_for
字典显示产生一个新的字典对象。
如果给出了以逗号分隔的键/数据对序列,则从左到右计算它们以定义字典的条目:每个键对象用作字典中的键以存储相应的数据。 这意味着您可以在键/数据列表中多次指定相同的键,并且该键的最终字典值将是最后一个给出的值。
双星号 **
表示 字典解包 。 它的操作数必须是 映射 。 每个映射项都添加到新字典中。 较晚的值替换由较早的键/数据对和较早的字典解包已经设置的值。
3.5 版新功能: 解包成字典显示,最初由 PEP 448 提出。
与列表推导式和集合推导式相比,字典推导式需要两个用冒号分隔的表达式,后跟通常的“for”和“if”子句。 当理解运行时,生成的键和值元素按照它们的生成顺序插入到新字典中。
键值类型的限制在前面的 标准类型层次结构 部分中列出。 (总而言之,键类型应该是 hashable,它排除了所有可变对象。)不会检测到重复键之间的冲突; 为给定键值存储的最后一个数据(显示中最右侧的文本)优先。
3.8 版更改: 在 Python 3.8 之前,在 dict 推导中,键和值的计算顺序没有明确定义。 在 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))
。
在只有一个参数的调用中可以省略括号。 有关详细信息,请参阅 调用 部分。
为了避免干扰生成器表达式本身的预期操作,隐式定义的生成器中禁止使用 yield
和 yield from
表达式。
如果生成器表达式包含 async for
子句或 await 表达式,则称为 异步生成器表达式 。 异步生成器表达式返回一个新的异步生成器对象,它是一个异步迭代器(参见 Asynchronous Iterators)。
3.6 版新功能: 引入了异步生成器表达式。
3.7 版更改: 在 Python 3.7 之前,异步生成器表达式只能出现在 async def 协程中。 从 3.7 开始,任何函数都可以使用异步生成器表达式。
3.8 版更改:在隐式嵌套作用域中禁止 yield
和 yield from
。
6.2.9. 产量表达式
yield_atom ::= "(" yield_expression ")" yield_expression ::= "yield" [expression_list | "from" expression]
在定义 generator 函数或 asynchronous generator 函数时使用 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 表达式,在那里它再次挂起,将 expression_list 的值返回给生成器的调用者。 挂起是指保留所有本地状态,包括本地变量的当前绑定、指令指针、内部评估堆栈以及任何异常处理的状态。 当通过调用生成器的方法之一恢复执行时,函数可以完全像 yield 表达式只是另一个外部调用一样继续执行。 恢复后 yield 表达式的值取决于恢复执行的方法。 如果使用 __next__()(通常通过 for 或 next() 内置),则结果为 None。 否则,如果使用 send(),则结果将是传递给该方法的值。
所有这些使得生成器函数与协程非常相似; 它们多次产生,它们有多个入口点并且它们的执行可以被暂停。 唯一的区别是生成器函数无法控制在它产生后继续执行的位置; 控制总是转移到生成器的调用者。
Yield 表达式可以在 try 结构中的任何地方使用。 如果生成器在完成之前没有恢复(通过达到零引用计数或通过垃圾收集),生成器迭代器的 close() 方法将被调用,允许任何挂起的 finally[ X220X] 子句来执行。
当使用 yield from <expr>
时,提供的表达式必须是可迭代的。 通过迭代迭代产生的值直接传递给当前生成器方法的调用者。 使用 send() 传入的任何值和使用 throw() 传入的任何异常都将传递给底层迭代器(如果它具有适当的方法)。 如果不是这种情况,那么 send() 将引发 AttributeError 或 TypeError,而 throw() 只会引发传递的在异常立即。
当底层迭代器完成时,引发的 StopIteration 实例的 value
属性成为 yield 表达式的值。 它可以在引发 StopIteration 时显式设置,也可以在子迭代器是生成器时自动设置(通过从子生成器返回值)。
3.3 版更改: 添加
yield from <expr>
以将控制流委托给子迭代器。
当 yield 表达式是赋值语句右侧的唯一表达式时,可以省略括号。
也可以看看
- PEP 255 - 简单的生成器
- 将生成器和 yield 语句添加到 Python 的提议。
- 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 异常。
- generator.send(value)
- 恢复执行并将值“发送”到生成器函数中。 value 参数成为当前产量表达式的结果。 send() 方法返回生成器生成的下一个值,或者如果生成器退出而没有生成另一个值,则引发 StopIteration。 当调用 send() 来启动生成器时,必须以 None 作为参数调用,因为没有可以接收值的 yield 表达式。
- generator.throw(type[, value[, traceback]])
- 在生成器暂停时引发类型为
type
的异常,并返回生成器函数生成的下一个值。 如果生成器退出而没有产生另一个值,则会引发 StopIteration 异常。 如果生成器函数没有捕获传入的异常,或者引发不同的异常,则该异常会传播给调用者。
- 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 语句中使用生成器对象。
调用异步生成器的方法之一会返回一个 awaitable 对象,并且在等待该对象时开始执行。 此时,执行继续执行第一个 yield 表达式,在那里它再次暂停,将 expression_list 的值返回给等待的协程。 与生成器一样,暂停意味着保留所有本地状态,包括本地变量的当前绑定、指令指针、内部计算堆栈和任何异常处理的状态。 当通过等待异步生成器的方法返回的下一个对象恢复执行时,该函数可以像 yield 表达式只是另一个外部调用一样继续执行。 恢复后 yield 表达式的值取决于恢复执行的方法。 如果使用 __anext__()
,则结果为 None。 否则,如果使用 asend()
,则结果将是传递给该方法的值。
如果异步生成器碰巧因 break、调用方任务被取消或其他异常提前退出,则生成器的异步清理代码将运行并可能在意外上下文中引发异常或访问上下文变量——也许在它所依赖的任务的生命周期,或者在调用异步生成器垃圾收集钩子时的事件循环关闭期间。 为了防止这种情况,调用者必须通过调用 aclose()
方法来显式关闭异步生成器,以完成生成器并最终将其从事件循环中分离。
在异步生成器函数中,在 try 构造中的任何位置都允许使用 yield 表达式。 但是,如果异步生成器在完成之前没有恢复(通过达到零引用计数或通过垃圾收集),那么 try
构造中的 yield 表达式可能导致执行挂起 失败]finally 子句。 在这种情况下,运行异步生成器的事件循环或调度器有责任调用异步生成器-迭代器的 aclose()
方法并运行生成的协程对象,从而允许任何挂起的 finally
子句执行。
为了在事件循环终止时处理终结,事件循环应该定义一个 finalizer 函数,该函数采用异步生成器-迭代器,并可能调用 aclose()
并执行协程。 这个 finalizer 可以通过调用 sys.set_asyncgen_hooks() 来注册。 当第一次迭代时,异步生成器-迭代器将存储注册的 finalizer 以在完成时调用。 有关 finalizer 方法的参考示例,请参阅 :source:`Lib/asyncio/base_events.py` 中 asyncio.Loop.shutdown_asyncgens
的实现。
表达式 yield from <expr>
在异步生成器函数中使用时会出现语法错误。
6.2.9.4. 异步生成器-迭代器方法
本小节描述异步生成器迭代器的方法,用于控制生成器函数的执行。
6.3. 初选
原色代表了语言中绑定最紧密的操作。 它们的语法是:
primary ::= atom | attributeref | subscription | slicing | call
6.3.1. 属性引用
属性引用是一个primary,后跟一个句点和一个名称:
attributeref ::= primary "." identifier
主要对象必须评估为支持属性引用的类型的对象,大多数对象都这样做。 然后要求该对象生成名称为标识符的属性。 此生产可以通过覆盖 __getattr__()
方法进行定制。 如果此属性不可用,则会引发异常 AttributeError。 否则,产生的对象的类型和值由对象决定。 对同一属性引用的多次评估可能会产生不同的对象。
6.3.2. 订阅
订阅序列(字符串、元组或列表)或映射(字典)对象通常从集合中选择一个项目:
subscription ::= primary "[" expression_list "]"
主要必须评估为支持订阅的对象(例如列表或字典)。 用户定义的对象可以通过定义 __getitem__()
方法来支持订阅。
对于内置对象,支持订阅的对象有两种:
如果主要是映射,则表达式列表必须计算其值为映射键之一的对象,并且订阅选择映射中与该键对应的值。 (表达式列表是一个元组,除非它只有一个项目。)
如果主是一个序列,则表达式列表的计算结果必须为整数或切片(如下一节所述)。
形式语法对序列中的负索引没有特殊规定; 但是,内置序列都提供了 __getitem__()
方法,该方法通过将序列的长度添加到索引来解释负索引(以便 x[-1]
选择 x
的最后一项) )。 结果值必须是小于序列中项目数的非负整数,订阅选择索引为该值的项目(从零开始计数)。 由于对负索引和切片的支持发生在对象的 __getitem__()
方法中,因此覆盖此方法的子类将需要显式添加该支持。
字符串的项是字符。 字符不是单独的数据类型,而是一个由一个字符组成的字符串。
订阅某些 类 或 类型 创建一个 通用别名 。 在这种情况下,用户定义的类可以通过提供 __class_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__()
方法)使用从切片列表构造的键,如下所示。 如果切片列表至少包含一个逗号,则键是一个包含切片项转换的元组; 否则,孤片项的转换是关键。 作为表达式的切片项目的转换就是该表达式。 正确切片的转换是一个切片对象(参见 标准类型层次结构),其 start
、stop
和 step
属性是分别以下限、上限和步幅给出的表达式,用 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 异常。 否则,填充槽的列表将用作调用的参数列表。
如果位置参数比形式参数槽多,则会引发 TypeError 异常,除非存在使用语法 *identifier
的形式参数; 在这种情况下,该形式参数接收一个包含多余位置参数的元组(如果没有多余的位置参数,则接收一个空元组)。
如果任何关键字参数与形式参数名称不对应,则会引发 TypeError 异常,除非存在使用语法 **identifier
的形式参数; 在这种情况下,该形式参数接收一个包含多余关键字参数的字典(使用关键字作为键,参数值作为相应的值),如果没有多余的关键字参数,则接收一个(新的)空字典。
如果语法 *expression
出现在函数调用中,则 expression
必须计算为 iterable。 来自这些可迭代对象的元素被视为额外的位置参数。 对于调用 f(x1, x2, *y, x3, x4)
,如果 y 计算结果为序列 y1, …, yM,这相当于调用 M+4位置参数 x1、x2、y1、……、yM、x3、、 ]。
这样做的结果是,尽管 *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 异常。
使用语法 *identifier
或 **identifier
的形式参数不能用作位置参数槽或关键字参数名称。
3.5 版更改: 函数调用接受任意数量的 *
和 **
解包,位置参数可以跟随可迭代解包(*
)和关键字参数可以遵循字典解包 (**
)。 最初由 PEP 448 提出。
调用总是返回一些值,可能是 None
,除非它引发异常。 该值的计算方式取决于可调用对象的类型。
如果是-
- 用户定义函数:
- 执行函数的代码块,将参数列表传递给它。 代码块要做的第一件事是将形参绑定到实参; 这在 函数定义 部分进行了描述。 当代码块执行 return 语句时,它指定函数调用的返回值。
- 内置函数或方法:
- 结果取决于解释器; 有关内置函数和方法的说明,请参阅 内置函数 。
- 一个类对象:
- 返回该类的新实例。
- 一个类实例方法:
- 相应的用户定义函数被调用,参数列表比调用的参数列表长一个:实例成为第一个参数。
- 一个类实例:
- 该类必须定义一个
__call__()
方法; 效果与调用该方法的效果相同。
6.5. 电力运营商
幂运算符比左边的一元运算符绑定得更紧密; 它的绑定不如右侧的一元运算符紧密。 语法是:
power ::= (await_expr | primary) ["**" u_expr]
因此,在无括号的幂和一元运算符序列中,运算符是从右到左计算的(这不限制操作数的计算顺序):-1**2
结果为 -1
。
幂运算符与内置 pow() 函数具有相同的语义,当使用两个参数调用时:它产生其左参数的右参数的幂。 数字参数首先被转换为一个通用类型,结果就是该类型。
对于 int 操作数,结果与操作数具有相同的类型,除非第二个参数为负数; 在这种情况下,所有参数都转换为浮点数,并提供浮点数结果。 例如,10**2
返回 100
,而 10**-2
返回 0.01
。
将 0.0
提高到负幂会导致 ZeroDivisionError。 将负数提高到分数幂会产生 复数 数。 (在早期版本中,它引发了 ValueError。)
可以使用特殊的 __pow__()
方法自定义此操作。
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 类型实现此运算符。
3.5 版中的新功能。
/
(除法)和 //
(底除法)运算符产生它们参数的商。 数字参数首先转换为通用类型。 整数的除法产生一个浮点数,而整数的底除法产生一个整数; 结果是将“floor”函数应用于结果的数学除法。 除以零会引发 ZeroDivisionError 异常。
可以使用特殊的 __truediv__()
和 __floordiv__()
方法自定义此操作。
%
(模)运算符产生第一个参数除以第二个参数的余数。 数字参数首先转换为通用类型。 零右参数会引发 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__()
方法进行定制。
没有为复数定义地板除法运算符、模运算符和 divmod() 函数。 相反,如果合适,使用 abs() 函数转换为浮点数。
+
(加法)运算符产生其参数的总和。 参数必须要么都是数字,要么都是相同类型的序列。 在前一种情况下,数字被转换为通用类型,然后相加。 在后一种情况下,序列被连接。
可以使用特殊的 __add__()
和 __radd__()
方法自定义此操作。
-
(减法)运算符产生其参数的差异。 数字参数首先转换为通用类型。
可以使用特殊的 __sub__()
方法自定义此操作。
6.8. 移位操作
移位运算的优先级低于算术运算:
shift_expr ::= a_expr | shift_expr ("<<" | ">>") a_expr
这些运算符接受整数作为参数。 他们将第一个参数向左或向右移动第二个参数给出的位数。
可以使用特殊的 __lshift__()
和 __rshift__()
方法自定义此操作。
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__()
或 __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"
比较产生布尔值:True
或 False
。 自定义 丰富的比较方法 可能会返回非布尔值。 在这种情况下,Python 将在布尔上下文中对此类值调用 bool()。
比较可以任意链接,例如,x < y <= z
等价于 x < y and y <= z
,除了 y
只计算一次(但在这两种情况下 z
都不计算)当 x < y
被发现是假的时)。
形式上,如果 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
并不意味着 a 和 c 之间的任何类型的比较,因此,例如,x < y > z
是完全合法的(尽管也许不漂亮)。
6.10.1. 价值比较
运算符 <
、>
、==
、>=
、<=
和 !=
比较两个对象的值. 对象不需要具有相同的类型。
第 章对象、值和类型 说明对象具有值(除了类型和标识之外)。 对象的值在 Python 中是一个相当抽象的概念:例如,对象的值没有规范的访问方法。 此外,不要求对象的值应该以特定方式构造,例如 由其所有数据属性组成。 比较运算符实现了对象值是什么的特定概念。 人们可以将它们视为通过比较实现间接定义对象的值。
因为所有类型都是 object 的(直接或间接)子类型,所以它们继承了 object 的默认比较行为。 类型可以通过实现 丰富的比较方法 来自定义它们的比较行为,例如 __lt__()
,在 基本自定义 中进行了描述。
相等比较的默认行为(==
和 !=
)基于对象的身份。 因此,相同身份的实例的相等比较导致相等,不同身份的实例的相等比较导致不平等。 这种默认行为的动机是希望所有对象都应该是自反的(即 x is y
意味着 x == y
)。
未提供默认顺序比较(<
、>
、<=
和 >=
); 尝试引发 TypeError。 这种默认行为的动机是缺乏与平等类似的不变量。
默认相等比较的行为,即具有不同身份的实例总是不相等的,可能与需要具有对象值和基于值的相等的合理定义的类型形成对比。 这些类型需要自定义它们的比较行为,事实上,许多内置类型已经这样做了。
下面的列表描述了最重要的内置类型的比较行为。
内置数字类型 (Numeric Types — int, float, complex) 和标准库类型 fractions.Fraction 和 decimal.Decimal 的数量可以是在它们的类型内和之间进行比较,限制是复数不支持顺序比较。 在所涉及类型的限制内,它们在数学上(算法上)进行比较而不会损失精度。
非数字值
float('NaN')
和decimal.Decimal('NaN')
是特殊的。 任何数字与非数字值的有序比较都是假的。 一个违反直觉的含义是非数字值不等于它们自己。 例如,如果x = float('NaN')
、3 < x
、x < 3
和x == x
都为假,而x != x
为真。 此行为符合 IEEE 754。None
和NotImplemented
是单例。 PEP 8 建议单例的比较应该始终使用is
或is not
,而不是相等运算符。二进制序列(bytes 或 bytearray 的实例)可以在它们的类型内部和之间进行比较。 它们使用其元素的数值按字典顺序进行比较。
字符串(str 的实例)使用其字符的数字 Unicode 代码点(内置函数 ord() 的结果)按字典顺序进行比较。 3
字符串和二进制序列不能直接比较。
序列(tuple、list 或 range 的实例)只能在它们的每个类型内进行比较,限制范围不支持顺序比较。 这些类型之间的相等比较会导致不平等,并且这些类型之间的排序比较会引发 TypeError。
序列使用对应元素的比较按字典顺序进行比较。 内置容器通常假定相同的对象与它们自己相等。 这让他们可以绕过相同对象的相等性测试,以提高性能并保持其内部不变量。
内置集合之间的字典比较工作如下:
两个集合要比较相等,它们必须是相同的类型,具有相同的长度,并且每对对应的元素必须比较相等(例如,
[1,2] == (1,2)
为假,因为类型不相同)。支持顺序比较的集合的顺序与其第一个不相等的元素相同(例如,
[1,2,x] <= [1,2,y]
与x <= y
具有相同的值)。 如果对应的元素不存在,则先对较短的集合进行排序(例如,[1,2] < [1,2,3]
为真)。
映射(dict 的实例)比较相等当且仅当它们具有相等的 (key, value) 对。 键和值的相等比较强制了自反性。
顺序比较(
<
、>
、<=
和>=
)会引发 TypeError。集合(set 或 frozenset 的实例)可以在它们的类型内部和之间进行比较。
他们定义顺序比较运算符来表示子集和超集测试。 这些关系不定义总排序(例如,两个集合
{1,2}
和{2,3}
不相等,彼此的子集也不相等,彼此的超集也不相等)。 因此,集合不是依赖于总排序的函数的合适参数(例如,min()、max() 和 sorted() 产生未定义结果给出了一个集合列表作为输入)。集合的比较强制其元素的自反性。
大多数其他内置类型没有实现比较方法,因此它们继承了默认的比较行为。
如果可能,自定义其比较行为的用户定义类应遵循一些一致性规则:
平等比较应该是自反的。 换句话说,相同的对象应该比较相等:
x is y
表示x == y
比较应该是对称的。 换句话说,以下表达式应该具有相同的结果:
x == y
和y == x
x != y
和y != x
x < y
和y > x
x <= y
和y >= 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. 会员测试操作
运算符 in 和 not in 测试成员资格。 如果 x 是 s 的成员,则 x in s
计算为 True
,否则计算为 False
。 x 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)
。
对于字符串和字节类型,x in y
是 True
当且仅当 x 是 y 的子串。 等效的测试是 y.find(x) != -1
。 空字符串总是被认为是任何其他字符串的子字符串,因此 "" in "abc"
将返回 True
。
对于定义 __contains__()
方法的用户定义类,如果 y.__contains__(x)
返回真值,x in y
返回 True
,否则返回 False
。
对于没有定义 __contains__()
但定义了 __iter__()
的用户定义类,x in y
是 True
如果某个值 z
,对于表达式 x is z or x == z
为真,是在迭代 y
时产生的。 如果在迭代期间引发异常,就好像 in 引发了该异常。
最后,尝试旧式迭代协议:如果一个类定义了 __getitem__()
,则 x in y
是 True
当且仅当存在非负整数索引 i 使得 x is y[i] or x == y[i]
,并且没有更低的整数索引会引发 IndexError 异常。 (如果引发任何其他异常,就好像 in 引发了该异常)。
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
在布尔运算的上下文中,以及当控制流语句使用表达式时,以下值被解释为 false:False
、None
、所有类型的数字零和空字符串和容器(包括字符串、元组、列表、字典、集合和冻结集合)。 所有其他值都被解释为真。 用户定义的对象可以通过提供 __bool__()
方法来自定义其真值。
如果参数为假,运算符 not 产生 True
,否则产生 False
。
表达式 x and y
首先计算 x; 如果 x 为假,则返回其值; 否则,计算 y 并返回结果值。
表达式 x or y
首先计算 x; 如果 x 为真,则返回其值; 否则,计算 y 并返回结果值。
请注意, 和 和 或 都不会限制它们返回的值和类型,它们返回 False
和 True
,而是返回最后评估的参数。 这有时很有用,例如,如果 s
是一个字符串,如果它是空的,则应该用默认值替换,表达式 s or 'foo'
会产生所需的值。 因为 not 必须创建一个新值,它返回一个布尔值而不考虑其参数的类型(例如,not 'foo'
生成 False
而不是 [ X173X]。)
6.12. 赋值表达式
assignment_expression ::= [identifier ":="] expression
赋值表达式(有时也称为“命名表达式”或“海象”)将 表达式 赋值给 标识符 ,同时还返回 表达式 的值.
一个常见的用例是处理匹配的正则表达式:
if matching := pattern.search(data):
do_something(matching)
或者,在分块处理文件流时:
while chunk := file.read(9000):
process(chunk)
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_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
除了列表或集合的一部分显示时,包含至少一个逗号的表达式列表会产生一个元组。 元组的长度是列表中表达式的数量。 表达式从左到右计算。
星号 *
表示 可迭代解包 。 它的操作数必须是 iterable。 可迭代对象被扩展为一系列项目,这些项目包含在解包现场的新元组、列表或集合中。
3.5 版新功能: 表达式列表中的可迭代解包,最初由 PEP 448 提出。
尾随逗号仅用于创建单个元组(又名 单例 ); 在所有其他情况下它是可选的。 没有尾随逗号的单个表达式不会创建元组,而是生成该表达式的值。 (要创建一个空元组,请使用一对空括号:()
。)
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
|
等待表达式 |
**
|
求幂 5 |
+x 、-x 、~x
|
正、负、按位非 |
* 、@ 、/ 、// 、%
|
乘法、矩阵乘法、除法、底除法、余数6 |
+ 、-
|
加减 |
<< 、>>
|
转移 |
&
|
按位与 |
^
|
按位异或 |
按位或 | |
在,不在,是,不是,< ,<= ,> 、>= 、!= 、==
|
比较,包括成员资格测试和身份测试 |
not x
|
布尔非 |
and
|
布尔与 |
or
|
布尔或 |
if – else
|
条件表达式 |
lambda
|
Lambda 表达式 |
:=
|
赋值表达式 |
脚注
- 1
虽然
abs(x%y) < abs(y)
在数学上是正确的,但对于浮点数,由于四舍五入,它在数值上可能不正确。 例如,假设 Python 浮点数是 IEEE 754 双精度数的平台,为了-1e-100 % 1e100
与1e100
具有相同的符号,计算结果为 [ X185X],在数值上完全等于1e100
。 函数 math.fmod() 返回一个结果,其符号与第一个参数的符号匹配,因此在这种情况下返回-1e-100
。 哪种方法更合适取决于应用程序。- 2
如果 x 非常接近 y 的精确整数倍,由于四舍五入,
x//y
可能比(x-x%y)//y
大 1。 在这种情况下,Python 返回后一个结果,以保持divmod(x,y)[0] * y + x % y
与x
非常接近。- 3
Unicode 标准区分 代码点 (例如 U+0041) 和 抽象字符 (例如 “拉丁文大写字母 A”)。 虽然 Unicode 中的大多数抽象字符仅使用一个代码点表示,但还有许多抽象字符可以另外使用多个代码点的序列表示。 例如,抽象字符“LATIN CAPITAL LETTER C WITH CEDILLA”可以表示为代码位置 U+00C7 处的单个 预组合字符 ,或表示为 基本字符 处的序列代码位置 U+0043(拉丁文大写字母 C),后跟代码位置 U+0327(组合 CEDILLA)处的 组合字符 。
字符串上的比较运算符在 Unicode 代码点级别进行比较。 这可能与人类的直觉相反。 例如,
"\u00C7" == "\u0043\u0327"
是False
,即使两个字符串都表示相同的抽象字符“拉丁大写字母 C WITH CEDILLA”。要在抽象字符级别(即以对人类直观的方式)比较字符串,请使用 unicodedata.normalize()。
- 4
由于自动垃圾收集、空闲列表和描述符的动态特性,您可能会注意到在 is 运算符的某些使用中看似异常的行为,例如涉及实例方法或常量之间的比较的行为。 查看他们的文档以获取更多信息。
- 5
幂运算符
**
的绑定不如其右侧的算术或按位一元运算符紧密,即2**-1
是0.5
。- 6
%
运算符也用于字符串格式化; 相同的优先级适用。