6. 表达式¶
本章将解释 Python 中组成表达式的各种元素的含义。
语法注释: 在本章和后续章节中,将使用 语法标记 来描述语法,而不是词法分析。
当(某种替代的)语法规则具有如下形式:
name: othername
并且没有给出语义,则这种形式的 name 在语法上与 othername 相同。
6.1. 算术转换¶
当下面某个算术运算符的描述中使用了“数字参数被转换为通用实数类型”这种说法时,则意味着针对内置数字类型的运算符实现的工作方式符合标准库文档 数字类型 一节的描述。
某些附加规则会作用于特定的运算符和非数字操作数(例如,字符串作为 % 运算符的左侧参数)。 扩展必须定义它们自己的转换行为。
6.2. 原子¶
原子是最基本的表达式元素。 最简单的原子有 内置常量, 名称 和 字面值。 更复杂的原子会以成对的分隔符标明:
原子的正式语法如下:
atom: |builtin_constant|identifier|literal|parenthesized_enclosure|bracketed_enclosure|braced_enclosureparenthesized_enclosure: |group|tuple|yield_atom|generator_expressionbracketed_enclosure: |listcomp|listbraced_enclosure: |dictcomp|dict|setcomp|set
6.2.1. 内置常量¶
关键字 True, False 和 None 为 内置常量。 词元 ... 为 Ellipsis 常量。
对这些原子执行求值将产生相应的值。
备注
还有一些内置常量可作为全局变量来访问,但只有在此提及的才属于 关键字。 具体而言,这些名称不可被赋值或用作属性:
>>> False = 123
File "<input>", line 1
False = 123
^^^^^
SyntaxError: cannot assign to False
内置常量的正式语法为:
builtin_constant: 'True' | 'False' | 'None' | '...'
6.2.2. 标识符(名称)¶
作为原子出现的标识符叫做名称。 请参看 名称(标识符和关键字) 一节了解其词法定义,以及 命名与绑定 获取有关命名与绑定的文档。
当名称被绑定到一个对象时,对该原子求值将返回相应对象。 当名称未被绑定时,尝试对其求值将引发 NameError 异常。
6.2.2.1. 私有名称的 mangling¶
当类定义中出现的标识符,以两个或更多下划线开头,并且不以两个或更多下划线结尾,就称它为类的 private name 。
参见
更具体地,私有名称在其字节码生成之前即被转为更长的名字。如果转换后的名字长于255字符,实现可以决定缩短。
这一转换过程和标识符使用的语法上下文无关,仅有以下几种私有标识符会被mangle:
用作被分配或读取的变量的名字的,或者用作被访问的属性的名字的。
但是嵌套的函数、类和类型别名的
__name__属性不会被 mangle。导入的模块的名称,例如
import __spam中的__spam。 若模块属于一个包(即它的名称中有点号),这个名称 不会 被 mangle,比如import __foo.bar中的__foo不会被 mangle。导入的成员的名称,比如
from spam import __f中的__f。
转换规则的定义如下:
类名称,先移除全部的开头下划线并插入一个开头下划线,再插入到标识符的前面,例如出现在名为
Foo,_Foo或__Foo类中的标识符__spam将被转换为_Foo__spam。如果类名称仅由下划线组成,则转换为标识符本身,例如出现在名为
_或__类中的标识符__spam将保持原样。
6.2.3. 字面值¶
字面值 是指值的文字表示形式。 Python 支持数字、字符串和字节串字面值。 格式字符串 和 模板字符串 可被视为字符串字面值。
数字字面值由单个的 NUMBER 词元构成,它表示一个整数、浮点数或虚数。 详情参见词法分析文档中的 数值字面量 一节。
字符串和字节串字面值可能由多个词元构成。 请参阅 字符串字面值合并 一节了解详情。
请注意负数和复数,如 -3 或 3+4.2j,从语法上说不算字面值,而是使用了 - 或 + 运算符的 单目 或 双目 算术运算。
对字面值进行求值将产生一个具有给定值的给定类型对象 (int, float, complex, str, bytes 或 Template)。 对于浮点数和虚数字面值来说这个值可能为近似值。
字面值的正式语法:
literal:strings|NUMBER
6.2.3.1. 字面值和对象标识¶
所有字面值都对应于不可变数据类型,因此对象标识的重要性不如其实际值。 多次对具有相同值的字面值求值(不论是发生在程序文本的相同位置还是不同位置)可能得到相同对象或是具有相同值的不同对象。
CPython 实现细节
例如,在 CPython 中,具有相同值的 小 整数将被求值为相同对象:
>>> x = 7
>>> y = 7
>>> x is y
True
但是,大整数将被求值为不同对象:
>>> x = 123456789
>>> y = 123456789
>>> x is y
False
此行为在未来的 CPython 版本中可能发生变化。 具体而言,在过去 "小" 和 "大" 整数之间的界限已经发生了变化。
CPython 会在你使用 is 比较字面值时发出 SyntaxWarning:
>>> x = 7
>>> x is 7
<input>:1: SyntaxWarning: "is" with 'int' literal. Did you mean "=="?
True
请参阅 什么情况下可以依靠 is 运算符进行对象的身份相等性测试? 了解更多信息。
模板字符串 是不可变对象但可能会引用可变对象作为 Interpolation 值。 对于本节内容而言,两个 t-字符串如果其结构和值的 标识号 都是匹配的就会具有“相同的值”。
目前,模板字符串的每次求值都会产生一个不同的对象。
6.2.3.2. 字符串字面值合并¶
允许使用多个相邻的字符串或字节字面量,这些字面量可能采用不同的引号规范;它们的含义等同于将其拼接在一起后的结果:
>>> "hello" 'world'
"helloworld"
此特性是在语法层面定义的,因此仅适用于字面量。若要在运行时拼接字符串表达式,可以使用 '+' 运算符:
>>> greeting = "Hello"
>>> space = " "
>>> name = "Blaise"
>>> print(greeting + space + name) # 不是: print(greeting space name)
Hello Blaise
字面量拼接可以自由混合原始字符串、三引号字符串和格式化字符串字面量。例如:
>>> "Hello" r', ' f"{name}!"
"Hello, Blaise!"
此特性可用于减少所需的反斜杠数量,方便地将长字符串分割到多行,甚至还能为字符串的特定部分添加注释。例如:
re.compile("[A-Za-z_]" # 字母或下划线
"[A-Za-z0-9_]*" # 字母、数字或下划线
)
不过,字节字面量只能与其他字节字面量组合,不能与任何类型的字符串字面量组合。此外,模板字符串字面量也只能与其他模板字符串字面量组合。
>>> t"Hello" t"{name}!"
Template(strings=('Hello', '!'), interpolations=(...))
形式上:
strings: (STRING|fstring)+ |tstring+
6.2.4. 带圆括号的分组¶
带圆括号的分组 是用圆括号括起来的表达式。 分组将被求值为与里面的表达式相同的值。
分组被用于覆盖或明确 运算符优先级,采用与数学标记法相同的方式。 例如:
>>> 3 << 2 | 4
12
>>> 3 << (2 | 4) # 覆盖 | (按位 OR) 的优先级
192
>>> (3 << 2) | 4 # 相当于没有圆括号(但更清楚)
12
请注意,括号内的内容并不都是 组。具体而言,括号内的组必须恰好包含一个表达式,且不能以逗号结尾。其他括号形式请参阅 tuple displays 和 generator expressions。
从形式上看,组别的语法为:
group: '(' assignment_expression ')'
6.2.5. 容器显示式¶
在构建内置容器(列表、集合、元组或字典)时,Python 提供了名为 displays 的特殊语法。四种表示方式之间存在细微差异,具体将在以下章节中详细说明。不过,所有表示方式都由以逗号分隔、并置于成对分隔符之间的项组成。
例如,列表显示 是一系列置于方括号内的表达式:
>>> ["one", "two", "three"]
['one', 'two', 'three']
>>> [1 + 2, 2 + 3]
[3, 5]
在列表、元组和字典(但不包括集合)的显示中,该序列可能为空::
>>> [] # empty list
[]
>>> () # empty tuple
()
>>> {} # empty dictionary
{}
如果该序列不为空,项后可以附加一个额外的逗号,但这不会产生影响:
>>> ["one", "two", "three",] # note comma after "three"
['one', 'two', 'three']
备注
尾随逗号通常用于跨多行显示的场合(使用 implicit line joining),因此,当未来的程序员在末尾添加新条目时,无需修改现有行:
>>> [
... 'one',
... 'two',
... 'three',
... ]
['one', 'two', 'three']
在运行时,当对显示式求值时,所列各项会从左到右依次求值,并被放入一个类型合适的新容器中。
对于元组、列表和集合(但不包括字典)的显示,其中任何条目都可以加上星号(*)前缀。这表示 iterable unpacking。在运行时,带星号前缀的表达式必须求值为一个可迭代对象,其内容会被插入到容器中解包所在的位置。例如:
>>> numbers = (1, 2)
>>> [*numbers, 'word', *numbers]
[1, 2, 'word', 1, 2]
字典显示采用一种类似的机制,称为 dictionary unpacking,用双星号 (**) 表示。详情请参阅 字典显示。
更高级的显示形式是 推导式,其中项目通过一组循环和过滤指令进行计算。详情请参阅 推导式 部分。
在 3.5 版本加入: 显示中将对可迭代对象和字典进行拆包,最初由 PEP 448 提出。
6.2.5.1. 列表显示¶
列表显示 是用方括号括起来的可能为空的表达式系列。 例如:
>>> ["one", "two", "three"]
['one', 'two', 'three']
>>> ["one"] # 单元素列表
['one']
>>> [] # 空列表
[]
有关显示的通用信息请参阅 容器显示式。
列表显示的正确式语法为:
list: '[' [flexible_expression_list] ']'
6.2.5.2. 集合显示¶
set display 是一个由花括号括起来的*非空*表达式序列。例如:
>>> {"one", "two", "three"}
{'one', 'three', 'two'}
>>> {"one"} # One-element set
{'one'}
有关显示的通用信息请参阅 容器显示式。
空集没有特殊的语法。{} 字面量是一个 dictionary display,用于构建一个空字典。不带任何参数调用 set(),即可获得一个空集。
集合表示的正式语法为:
set: '{' flexible_expression_list '}'
6.2.5.3. 元组显示¶
tuple display 是一组置于括号内的表达式。例如:
>>> (1, 2)
(1, 2)
>>> () # an empty tuple
()
有关显示的通用信息请参阅 容器显示式。
为避免歧义,如果元组显示恰好包含一个元素,则必须使用末尾逗号;否则,将得到 parenthesized group:
>>> ('single',) # single-element tuple
('single',)
>>> ('single') # no comma: single string
'single'
换句话说,元组显示是由以下内容组成的带括号列表:
两个或多个以逗号分隔的表达式,或
零个或多个表达式,每个表达式后跟一个逗号。
由于元组是不可变的,object identity rules for literals 同样适用于元组:在运行时,两个具有相同值的元组实例可能产生相同的对象,也可能不产生相同的对象。
备注
Python 的语法还包含 expression lists,其中由逗号分隔的表达式列表*不*被置于括号内,但会求值为一个元组。
换句话说,对于元组的语法,逗号比圆括号的使用更重要。 只有空元组才可以不带逗号。
元组表示形式的正式语法为:
tuple: | '('flexible_expression(','flexible_expression)+ [','] ')' | '('flexible_expression',' ')' | '(' ')'
6.2.5.4. 字典显示¶
dictionary display 是一个可能为空、由花括号包裹的 dict items 序列。每个字典项由以冒号分隔的两个表达式组成:key 及其关联的 value。例如:
>>> {1: 'one', 2: 'two'}
{1: 'one', 2: 'two'}
在运行时,当字典推导式被求值时,表达式会从左到右依次求值。每个键对象都会作为字典的键,用于存储相应的值。这意味着你可以在推导式中多次指定同一个键,而最终字典中该键对应的值将是最后一次指定的值。例如:
>>> {
... 1: 'this will be overridden',
... 2: 'two',
... 1: 'also overridden',
... 1: 'one',
... }
{1: 'one', 2: 'two'}
与键值对不同,dict 项可以是以双星号 ** 作为前缀的表达式。这表示 dictionary unpacking。在运行时,该表达式必须求值为一个 映射;映射中的每个项都会被添加到新字典中。与键值对类似,后续的值会覆盖先前项及解包操作已设置的值。这可用于覆盖一组默认值:
>>> defaults = {'color': 'blue', 'count': 8}
>>> overrides = {'color': 'yellow'}
>>> {**defaults, **overrides}
{'color': 'yellow', 'count': 8}
在 3.5 版本加入: 拆包到字典显示,最初由 PEP 448 提出。
字典表示形式的正式语法为:
dict: '{' [double_starred_kvpairs] '}' double_starred_kvpairs: ','.double_starred_kvpair+ [','] double_starred_kvpair: '**'or_expr|kvpairkvpair:expression':'expression
6.2.6. 推导式¶
List、set 和 dictionary 推导式 是 container displays 的一种形式,其中项目通过一组循环和过滤指令进行计算,而非以显式列表形式呈现。
在最简单的形式中,一个 推导式 由单个表达式及紧随其后的 for 子句组成。for 子句的语法与 for statement 的 header 相同,但不含末尾的冒号。
例如,前十个方格的列表为:
>>> [x**2 for x in range(10)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
在运行时,列表推导会创建一个新列表。in 之后的表达式必须求值为一个 可迭代对象。对于该可迭代对象的每个元素,该元素会被绑定到 for 子句的目标上,其方式类似于 for 语句;随后,for 之前的表达式会在当前作用域中结合该目标进行求值,并将结果添加到新列表中。因此,上述示例大致等价于定义并调用以下函数:
def make_list_of_squares(iterable):
result = []
for x in iterable:
result.append(x**2)
return result
make_list_of_squares(range(10))
集合 推导式 的工作原理与之类似。例如,下面是一组小写字母:
>>> {x.lower() for x in ['a', 'A', 'b', 'C']}
{'c', 'a', 'b'}
在运行时,这大致相当于调用此函数:
def make_lowercase_set(iterable):
result = set()
for x in iterable:
result.add(x.lower())
return result
make_lowercase_set(['a', 'A', 'b', 'C'])
字典推导式以冒号分隔的键值对开始,而非表达式。例如:
>>> {func.__name__: func for func in [print, hex, any]}
{'print': <built-in function print>,
'hex': <built-in function hex>,
'any': <built-in function any>}
在运行时,这大致相当于:
def make_dict_mapping_names_to_functions(iterable):
result = {}
for func in iterable:
result[func.__name__] = func
return result
make_dict_mapping_names_to_functions([print, hex, any])
与其他类型的字典显示方式一样,同一个键可以多次指定。较早的值会被后续评估的值覆盖。
不存在 元组 推导式。取而代之的是,使用类似的语法来定义 generator expressions;通过它,你可以像这样构建一个元组:
>>> tuple(x**2 for x in range(10))
(0, 1, 4, 9, 16, 25, 36, 49, 64, 81)
在 3.8 版本发生变更: 在 Python 3.8 之前的字典推导式中,并没有定义好键和值的求值顺序。 在 CPython 中,值会先于键被求值。 根据 PEP 572 的提议,从 3.8 开始,键会先于值被求值。
6.2.6.1. 在理解中进行筛选¶
for 子句后可跟一个带有表达式的 if 子句。
例如,来自 math 模块且以 f 开头的名称列表如下:
>>> [name for name in vars(math) if name.startswith('f')]
['fabs', 'factorial', 'floor', 'fma', 'fmod', 'frexp', 'fsum']
在运行时,每个元素被添加到结果容器中之前,会先对 if 之后的表达式进行求值;如果结果为 false,则跳过该元素。因此,上述示例大致相当于定义并调用以下函数:
def get_math_f_names(iterable):
result = []
for name in iterable:
if name.startswith('f'):
result.append(name)
return result
get_math_f_names(vars(math))
过滤是更复杂推导式的一个特例。更正式的描述请参见下一节。
6.2.6.2. 复杂理解¶
通常,一个 推导式 的初始 for 子句之后可以跟随零个或多个额外的 for 或 if 子句。例如,以下是一个由两个 Python 模块返回的姓名列表,经过筛选后仅包含以 a 开头的姓名:
>>> import array
>>> import math
>>> [
... name
... for module in [array, math]
... for name in vars(module)
... if name.startswith('a')
... ]
['array', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh']
在运行时,这大致相当于定义并调用:
def get_a_names(iterable):
result = []
for module in iterable:
for name in vars(module):
if name.startswith('a'):
result.append(name)
return result
get_a_names([array, math])
新容器的元素,是通过将 for 或 if 子句中的每一个视为一个块、从左到右进行嵌套,并在每次到达最内层块时评估该表达式以生成一个元素(或字典条目)而得到的。
除了最左侧 for 子句中的可迭代表达式外,推导式会在一个独立的、隐式嵌套的作用域中执行。这确保了在目标列表中赋值的名称不会“泄漏”到外层作用域中。例如:
>>> x = 'old value'
>>> [x**2 for x in range(10)] # this `x` is local to the comprehension
>>> x
'old value'
最左侧 for 子句中的可迭代表达式会在其 外围作用域 中直接求值,随后作为参数传递给隐式嵌套 scope。
后续的 for 子句以及最左侧 for 子句中的任何过滤条件,都无法在 外围作用域 中进行求值,因为它们可能依赖于从最左侧可迭代元素中获取的值。
为了确保推导式得出的结果总是一个类型正确的容器,在隐式嵌套作用域内禁止使用 yield 和 yield from 表达式。
Assignment expressions 不得出现在推导式的可迭代表达式内部(即 in 关键字之后的表达式中),也不得出现在直接出现在类定义中的 推导式 的任何位置。
在 3.8 版本发生变更: yield 和 yield from 在隐式嵌套的作用域中已被禁用。
6.2.6.3. 在理解层面展开分析¶
如果列表或集合 推导式 的表达式带有星号,则结果将为 unpacked,从而生成零个或多个元素。
这通常用于对列表进行“扁平化”处理,例如:
>>> students = ['Petr', 'Blaise', 'Jarka']
>>> teachers = ['Salim', 'Bartosz']
>>> lists_of_people = [students, teachers]
>>> [*people for people in lists_of_people]
['Petr', 'Blaise', 'Jarka', 'Salim', 'Bartosz']
在运行时,这种理解大致对应于:
def flatten_names(lists_of_people):
result = []
for people in lists_of_people:
result.extend(people)
return result
在字典解包中,双星号表达式将被求值,随后通过 dictionary unpacking 进行解包,将零个或多个键/值对插入到新字典中。与其他类型的字典显示方式相同,如果同一个键被多次指定,则最终字典中与之关联的值将是最后一次指定的值。
例如:
>>> system_defaults = {'color': 'blue', 'count': 8}
>>> user_defaults = {'color': 'yellow'}
>>> overrides = {'count': 5}
>>> configuration_sets = [system_defaults, user_defaults, overrides]
>>> {**d for d in configuration_sets}
{'color': 'yellow', 'count': 5}
在 3.15 版本加入: 使用 * 和 ** 运算符进行推导式中的展开的功能,在 PEP 798 中首次引入。
6.2.6.4. 异步理解¶
在 async def 函数中,可以使用 async for 子句对 异步迭代器 进行迭代。在 async def 函数中,求值体可由位于前置表达式之后的 for 或 async for 子句构成;也可包含额外的 for 或 async for 子句;此外还可使用 await 表达式。
如果某个 推导式 包含 async for 子句,或者除最左侧 for 子句中的 iterable 表达式外, anywhere 包含 await 表达式或其他异步 推导式,则称其为 asynchronous 推导式。异步 推导式 可能会暂停其中所出现的协程函数的执行。
在 3.6 版本加入: 异步理解已在 PEP 530 中引入。
在 3.11 版本发生变更: 现在允许在异步函数的推导式中使用异步推导式。 外部推导式将隐式地转为异步的。
6.2.6.5. 推导式正式语法¶
理解形式的正式语法为:
listcomp: '['comprehension']' setcomp: '{'comprehension'}' comprehension:flexible_expressionfor_if_clause+ dictcomp: | '{'kvpairfor_if_clause+ '}' | '{' '**'expressionfor_if_clause+ '}' for_if_clause: | ['async'] 'for'target_list'in'or_test('if'or_test)*
6.2.7. 生成器表达式¶
生成器表达式 的语法与列表 推导式 相同,不同之处在于前者被置于括号内,而非方括号内。例如:
>>> iterator = (x ** 2 for x in range(10))
>>> iterator
<generator object <genexpr> at ...>
在运行时,生成器表达式会求值为一个 生成器迭代器,其产生的值与相应的列表推导式相同:
>>> list(iterator)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
因此,上述示例大致等同于定义并调用以下生成器函数:
def make_generator_of_squares(iterator):
for x in iterator:
yield x ** 2
make_generator_of_squares(iter(range(10)))
当生成器表达式是唯一的位置参数且不存在关键字参数时,在调用中可以省略外层的括号。详情请参阅 Calls section。例如:
# The parentheses after `sum` are part of the call syntax:
>>> sum(x ** 2 for x in range(10))
285
# The generator needs its own parentheses if it's not the only argument:
>>> sum((x ** 2 for x in range(10)), start=1000)
1285
最左侧 for 子句中的可迭代表达式会立即求值,因此,该表达式所引发的错误会在生成器表达式定义的位置发出,而不是在获取第一个值的位置发出:
>>> (x ** 2 for x in nonexistent_iterable)
Traceback (most recent call last):
...
NameError: name 'nonexistent_iterable' is not defined
表达式求值后,会基于该结果创建一个迭代器,就好像对该迭代器调用了 iter() 一样。创建迭代器时产生的任何错误也会立即被抛出:
>>> (x ** 2 for x in None)
Traceback (most recent call last):
...
TypeError: 'NoneType' object is not iterable
所有其他表达式均采用惰性求值方式求值,其方式与常规生成器相同(即当迭代器被请求返回一个值时):
>>> iterator = (nonexistent_value for x in range(10))
>>> iterator
<generator object <genexpr> at ...>
>>> list(iterator)
Traceback (most recent call last):
...
NameError: name 'nonexistent_value' is not defined
>>> iterator = (x * y for x in range(10) for y in nonexistent_iterable)
>>> iterator
<generator object <genexpr> at ...>
>>> list(iterator)
Traceback (most recent call last):
...
NameError: name 'nonexistent_iterable' is not defined
为避免干扰生成器表达式本身的预期运行,禁止在隐式嵌套作用域内使用 yield 和 yield from 表达式。
如果生成器表达式包含 async for 子句或 await 表达式,则称其为 asynchronous generator expression。异步生成器表达式会返回一个新的异步生成器对象,该对象是一个异步迭代器(参见 异步迭代器)。
生成器表达式的形式语法为:
generator_expression: "(" comprehension ")"
在 3.6 版本加入: 引入了异步生成器表达式。
在 3.7 版本发生变更: 在 Python 3.7 之前,异步生成器表达式只能在 async def 协程中出现。 从 3.7 开始,任何函数都可以使用异步生成器表达式。
在 3.8 版本发生变更: yield 和 yield from 在隐式嵌套的作用域中已被禁用。
6.2.8. yield 表达式¶
yield_atom: "("yield_expression")" yield_from: "yield" "from"expressionyield_expression: "yield"yield_list|yield_from
yield 表达式在定义 generator 函数或 asynchronous generator 函数时才会用到,因此只能在函数定义的内部使用。 在一个函数体内使用 yield 表达式会使这个函数变成一个生成器函数,而在一个 async def 函数的内部使用它则会让这个协程函数变成一个异步生成器函数。 例如:
def gen(): # 定义一个生成器函数
yield 123
async def agen(): # 定义一个异步生成器函数
yield 123
由于它们会对外层作用域造成附带影响,yield 表达式不被允许作为用于实现推导式和生成器表达式的隐式定义作用域的一部分。
在 3.8 版本发生变更: 禁止在实现推导式和生成器表达式的隐式嵌套作用域中使用 yield 表达式。
下面是对生成器函数的描述,异步生成器函数会在 异步生成器函数 一节中单独介绍。
当一个生成器函数被调用时,它将返回一个名为生成器的迭代器。 然后这个生成器将控制生成器函数的执行。 执行过程会在这个生成器的某个方法被调用时开始。 这时,函数会执行到第一个 yield 表达式,在那里它将再次被挂起,向生成器的调用方返回 yield_list 的值,或者如果 yield_list 被省略则返回 None。 所谓的挂起,就是说所有局部状态都会被保留,包括局部变量的当前绑定、指令指针、内部求值栈及任何异常处理等等。 当通过调用生成器的某个方法恢复执行时,这个函数的运行就与 yield 表达式只是一个外部调用的情况完全一样。 在恢复执行后 yield 表达式的值取决于恢复执行所调用的方法。 如果是用 __next__() (一般是通过 for 或者 next() 内置函数) 则结果为 None。 在其他情况下,如果是用 send(),则结果将为传给该方法的值。
所有这些使生成器函数与协程非常相似;它们 yield 多次,它们具有多个入口点,并且它们的执行可以被挂起。唯一的区别是生成器函数不能控制在它在 yield 后交给哪里继续执行;控制权总是转移到生成器的调用者。
在 try 结构中的任何位置都允许 yield 表达式。如果生成器在(因为引用计数到零或是因为被垃圾回收)销毁之前没有恢复执行,将调用生成器-迭代器的 close() 方法,从而允许任何挂起的 finally 子句执行。
当使用 yield from <expr> 时,所提供的表达式必须是一个可迭代对象。 迭代该可迭代对象所产生的值会被直接传递给当前生成器方法的调用者。 任何通过 send() 传入的值以及任何通过 throw() 传入的异常如果有适当的方法则会被传给下层迭代器。 如果不是这种情况,那么 send() 将引发 AttributeError 或 TypeError,而 throw() 将立即引发所转入的异常。
当下层迭代器完成时,被引发的 StopIteration 实例的 value 属性会成为 yield 表达式的值。 它可以在引发 StopIteration 时被显式地设置,也可以在子迭代器是一个生成器时自动地设置(通过从子生成器返回一个值)。
在 3.3 版本发生变更: 添加 yield from <expr> 以委托控制流给一个子迭代器。
当yield表达式是赋值语句右侧的唯一表达式时,括号可以省略。
参见
6.2.8.1. 例子¶
这里是一个简单的例子,演示了生成器和生成器函数的行为:
>>> 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.8.2. 异步生成器函数¶
在一个使用 async def 定义的函数或方法中出现的 yield 表达式会进一步将该函数定义为一个 asynchronous generator 函数。
当一个异步生成器函数被调用时,它会返回一个名为异步生成器对象的异步迭代器。 此对象将在之后控制该生成器函数的执行。 异步生成器对象通常被用在协程函数的 async for 语句中,类似于在 for 语句中使用生成器对象。
调用某个异步生成器的方法将返回一个 awaitable 对象,执行会在此对象被等待时启动。 到那时,将执行至第一个 yield 表达式,在那里它会再次挂起,将 yield_list 的值返回给等待中的协程。 与生成器一样,挂起意味着所有局部状态会被保留,包括局部变量的当前绑定、指令指针、内部求值栈以及任何异常处理的状态。 当执行在等待异步生成器的方法返回下一个对象后恢复时,该函数可以从原状态继续执行,就仿佛 yield 表达式只是另一个外部调用那样。 恢复执行后 yield 表达式的值取决于恢复执行所用的方法。 如果是使用 __anext__() 则结果为 None。 否则的话,如果是使用 asend(),则结果将是传递给该方法的值。
如果异步生成器因 break 而意外提前退出、调用者任务被取消,或发生其他异常,生成器的异步清理代码将会执行,并可能在非预期上下文中引发异常或访问上下文变量——例如在其所依赖的任务生命周期结束后,或在事件循环关闭期间。为防止这种情况发生,调用者必须通过调用 aclose() 方法显式关闭该异步生成器,以完成生成器的最终化,并最终将其从事件循环中解绑。
在异步生成器函数中,yield 表达式可以出现在 try 构造体的任意位置。然而,如果异步生成器在最终化之前(即达到零引用计数或被垃圾回收时)未被恢复,那么 try 构造体中的 yield 表达式可能导致未执行的待处理 finally 子句无法执行。在这种情况下,负责运行该异步生成器的事件循环或调度器有责任调用异步生成器-迭代器的 aclose() 方法,并运行由此产生的 awaitable,从而允许任何待处理的 finally 子句得以执行。
当异步生成器即将被垃圾回收时,为处理最终化过程,事件循环应定义一个 finalizer 函数;该函数接收一个异步生成器-迭代器,并调度由 aclose() 返回的可 await 对象。可通过调用 sys.set_asyncgen_hooks() 来注册此 finalizer。当异步生成器-迭代器首次被遍历时,会将其注册的 finalizer 存储起来,以便在最终化时调用。关于 finalizer 方法的参考示例,请参阅 Lib/asyncio/base_events.py 中对 asyncio.BaseEventLoop._asyncgen_finalizer_hook 的实现。
yield from <expr> 表达式如果在异步生成器函数中使用会引发语法错误。
6.3. 原型¶
原型代表编程语言中最紧密绑定的操作。 它们的句法如下:
primary:atom|attributeref|subscription|call
6.3.1. 属性引用¶
属性引用是后面带有一个句点加一个名称的原型:
attributeref:primary"."identifier
此原型必须求值为一个支持属性引用的类型的对象,多数对象都支持此特性。 随后该对象会被要求产生以指定标识符为名称的属性。 所产生对象的类型和值会根据该对象来确定。 对同一属性引用的多次求值可能产生不同的对象。
产生过程可通过重载 __getattribute__() 方法或 __getattr__() 方法来自定义。 将会先调用 __getattribute__() 方法并返回一个值或者如果属性不可用则会引发 AttributeError。
如果引发了 AttributeError 并且对象具有 __getattr__() 方法,则将调用该方法作为回退项。
6.3.2. 抽取与切片¶
抽取 语法通常被用于从 容器 中选择一个元素 -- 例如,从 dict 取一个值:
>>> digits_by_name = {'one': 1, 'two': 2}
>>> digits_by_name['two'] # 使用键 'two' 进行对字典的抽取操作
2
在抽取语法中,被抽取的对象 -- 一个 原型 -- 后跟方括号中的 下标。 在最简单的情况下,下标是一个单独的表达式。
根据被抽取对象的类型,下标有时也称为 key (用于映射)、index (用于序列) 或 类型参数 (用于 泛型类型)。 在语法上,它们都是等价的:
>>> colors = ['red', 'blue', 'green', 'black']
>>> colors[3] # 使用索引号 3 对列表进行抽取
'black'
>>> list[str] # 使用类型参数 str 对 list 类型进行参数化
list[str]
在运行时,解释器将对原型和下标求值,并以下标作为参数调用原型的 __getitem__() 或 __class_getitem__() special method。 有关具体调用哪个方法的详情,请参阅 __class_getitem__ 与 __getitem__。
为了展示抽取的工作方式,我们可以定义一个实现了 __getitem__() 的自定义对象并打印出下标的值:
>>> class SubscriptionDemo:
... def __getitem__(self, key):
... print(f'subscripted with: {key!r}')
...
>>> demo = SubscriptionDemo()
>>> demo[1]
subscripted with: 1
>>> demo['a' * 3]
subscripted with: 'aaa'
有关内置类型如何处理抽取操作,请参阅 __getitem__() 文档。
抽取也可被用作 赋值 或 删除 语句的目标。 在这种情况下,解释器将分别调用被抽取对象的 __setitem__() 或 __delitem__() special method,而不是 __getitem__()。
>>> colors = ['red', 'blue', 'green', 'black']
>>> colors[3] = 'white' # 按索引设置条目
>>> colors
['red', 'blue', 'green', 'white']
>>> del colors[3] # 删除索引号为 3 的条目
>>> colors
['red', 'blue', 'green']
以下各节中所介绍的所有高级形式的 下标 同样可用于赋值和删除操作。
6.3.2.1. 切片¶
一种更高级的抽取形式 切片 通常被用于从 序列 中提取一部分。 在这种形式中,下标是一个 slice: 最多三个以冒号分隔的表达式。 其中任何表达式都可以省略,但切片必须包含至少一个冒号:
>>> number_names = ['zero', 'one', 'two', 'three', 'four', 'five']
>>> number_names[1:3]
['one', 'two']
>>> number_names[1:]
['one', 'two', 'three', 'four', 'five']
>>> number_names[:3]
['zero', 'one', 'two']
>>> number_names[:]
['zero', 'one', 'two', 'three', 'four', 'five']
>>> number_names[::2]
['zero', 'two', 'four']
>>> number_names[:-3]
['zero', 'one', 'two']
>>> del number_names[4:]
>>> number_names
['zero', 'one', 'two', 'three']
当对切片求值时,解释器会构造一个 slice 对象,其 start、stop 和 step 属性分别是冒号之间的表达式的求值结果。 缺失的表达式将求值为 None。 此 slice 对象随后会被传给 __getitem__() 或 __class_getitem__() special method,如前所述。
# 继续使用前面定义的 SubscriptionDemo 实例:
>>> demo[2:3]
subscripted with: slice(2, 3, None)
>>> demo[::'spam']
subscripted with: slice(None, None, 'spam')
6.3.2.2. 以逗号分隔的下标¶
下标也可以是两个或更多以逗号分隔的表达式或切片:
# 继续使用前面定义的 SubscriptionDemo 实例:
>>> demo[1, 2, 3]
subscripted with: (1, 2, 3)
>>> demo[1:2, 3]
subscripted with: (slice(1, 2, None), 3)
此形式常与数值库一起用于对多维数据进行切片。 在这种情况下,解释器将表达式或切片的结果构造为一个 tuple,并将该元组传给 __getitem__() 或 __class_getitem__() special method,如前所述。
下标也可以是一个单独的表达式或切片后跟一个逗号,以指定一个单元素的元组:
>>> demo['spam',]
subscripted with: ('spam',)
6.3.2.3. 带星号的抽取¶
在 3.11 版本加入: tuple_slices 中的表达式可以带星号。 参见 PEP 646。
下标也可以包含带星号的表达式。 在这种情况下,解释器将结果拆包为一个元组,并将该元组传给 __getitem__() 或 __class_getitem__():
# 继续使用前面定义的 SubscriptionDemo 实例:
>>> demo[*range(10)]
subscripted with: (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
带星号的表达式可以与以逗号分隔的表达式和切片组合使用:
>>> demo['a', 'b', *range(3), 'c']
subscripted with: ('a', 'b', 0, 1, 2, 'c')
6.3.2.4. 正式的抽取语法¶
subscription:primary'['subscript']' subscript:single_subscript|tuple_subscriptsingle_subscript:proper_slice|assignment_expressionproper_slice: [expression] ":" [expression] [ ":" [expression] ] tuple_subscript: ','.(single_subscript|starred_expression)+ [',']
回顾一下,| 运算符 表示有序选择。 具体来说,在 subscript 中,如果两个备选项都能匹配,则第一个 (single_subscript) 具有优先权。
6.3.3. 调用¶
所谓调用就是附带可能为空的一系列 参数 来执行一个可调用对象 (例如 function):
call:primary"(" [argument_list[","] |comprehension] ")" argument_list:positional_arguments[","starred_and_keywords] [","keywords_arguments] |starred_and_keywords[","keywords_arguments] |keywords_argumentspositional_arguments:positional_item(","positional_item)* positional_item:assignment_expression| "*"expressionstarred_and_keywords: ("*"expression|keyword_item) ("," "*"expression| ","keyword_item)* keywords_arguments: (keyword_item| "**"expression) (","keyword_item| "," "**"expression)* keyword_item:identifier"="expression
一个可选项为在位置和关键字参数后加上逗号而不影响语义。
此原型必须被求值为一个可调用对象(用户自定义函数、内置函数、内置对象的方法、类对象、类实例的方法以及任何具有 __call__() 方法的对象都是可调用对象)。 所有参数表达式将在尝试调用前被求值。 请参阅 函数定义 一节了解正式的 parameter 列表的语法。
如果存在关键字参数,它们会先通过以下操作被转换为位置参数。 首先,为正式参数创建一个未填充空位的列表。 如果有 N 个位置参数,则它们会被放入前 N 个空位。 然后,对于每个关键字参数,使用标识符来确定其对应的空位(如果标识符与第一个正式参数名相同则使用第一个空位,依此类推)。 如果空位已被填充,则会引发 TypeError 异常。 否则,将参数值放入空位,进行填充(即使表达式为 None,它也会填充空位)。 当所有参数处理完毕时,尚未填充的空位将用来自函数定义的相应默认值来填充。 (函数一旦被定义,其默认值就会被计算;因此,当列表或字典这类可变对象被用作默认值时将会被所有未指定相应空位参数值的调用所共享;这种情况通常应当被避免。) 如果任何一个未填充空位没有指定默认值,则会引发 TypeError 异常。 在其他情况下,已填充空位的列表会被作为调用的参数列表。
某些实现可能提供位置参数没有名称的内置函数,即使它们在文档说明的场合下有“命名”,因此不能以关键字形式提供参数。 在 CPython 中,以 C 编写并使用 PyArg_ParseTuple() 来解析其参数的函数实现就属于这种情况。
如果存在比正式参数空位多的位置参数,将会引发 TypeError 异常,除非有一个正式参数使用了 *identifier 句法;在此情况下,该正式参数将接受一个包含了多余位置参数的元组(如果没有多余位置参数则为一个空元组)。
如果任何关键字参数没有与之对应的正式参数名称,将会引发 TypeError 异常,除非有一个正式参数使用了 **identifier 句法,该正式参数将接受一个包含了多余关键字参数的字典(使用关键字作为键而参数值作为与键对应的值),如果没有多余关键字参数则为一个(新的)空字典。
如果函数调用中出现了 *expression 句法,expression 必须求值为一个 iterable。 来自该可迭代对象的元素会被当作是额外的位置参数。 对于 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 必须求值为一个 mapping,其内容会被当作是额外的关键字参数。 如果一个形参与一个已给定值关键字相匹配(通过显式的关键字参数,或通过另一个解包),则会引发 TypeError 异常。
当使用 **expression 时,该映射中的每个键都必须为字符串。 该映射中的每个值将被赋值给名称与键相同的适用于关键字赋值的第一个正式形参。 键名不需要是 Python 标识符(例如 "max-temp °F" 也是可接受的,但它将不能与可被声明的任何正式形参相匹配)。 如果键值对未与某个正式形参相匹配则将被 ** 形参所收集,或者如果没有此形参,则会引发 TypeError 异常。
使用 *identifier 或 **identifier 句法的正式参数不能被用作位置参数空位或关键字参数名称。
在 3.5 版本发生变更: 函数调用接受任意数量的 * 和 ** 拆包,位置参数可能跟在可迭代对象拆包 (*) 之后,而关键字参数可能跟在字典拆包 (**) 之后。 由 PEP 448 发起最初提议。
除非引发了异常,调用总是会有返回值,返回值也可能为 None。 返回值的计算方式取决于可调用对象的类型。
如果类型为---
- 用户自定义函数:
函数的代码块会被执行,并向其传入参数列表。 代码块所做的第一件事是将正式形参绑定到对应的参数;相关说明参见 函数定义 一节。 当代码块执行
return语句时,这将指定函数调用的返回值。 如果执行到达代码块的末尾时并未执行过return语句,则返回值将为None。- 内置函数或方法:
具体结果依赖于解释器;有关内置函数和方法的描述参见 内置函数。
- 类对象:
返回该类的一个新实例。
- 类实例方法:
调用相应的用户自定义函数,向其传入的参数列表会比调用的参数列表多一项:该实例将成为第一个参数。
- 类实例:
该类必须定义
__call__()方法;其效果将等价于调用该方法。
6.4. await 表达式¶
挂起 coroutine 的执行以等待一个 awaitable 对象。 只能在 coroutine function 内部使用。
await_expr: "await" primary
在 3.5 版本加入.
6.5. 幂运算符¶
幂运算符的绑定比在其左侧的一元运算符更紧密;但绑定紧密程度不及在其右侧的一元运算符。 句法如下:
power: (await_expr|primary) ["**"u_expr]
因此,在一个未加圆括号的幂运算符和单目运算符序列中,运算符将从右向左求值(这不会限制操作数的求值顺序): -1**2 结果将为 -1。
当以两个参数调用时,幂运算符的语义与内置函数 pow() 相同:它返回左参数以右参数为指数进行幂运算后的结果。数值型参数首先被 转换为共同的类型,结果也为此类型。
对于 int 类型的操作数,结果将具有与操作数相同的类型,除非第二个参数为负数;在那种情况下,所有参数会被转换为 float 类型并输出 float 类型的结果。 例如,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_expra_expr:m_expr|a_expr"+"m_expr|a_expr"-"m_expr
* (乘法)运算符会返回其参数的乘积。参数必须同时为数字,或者其中一个参数为整数而另一个为序列。在前一种情况下,两个数字通过 转换为共同的实数类型 后再相乘;在后一种情况下,则执行序列重复操作;若重复因子为负数,则生成空序列。
此运算可使用特殊的 __mul__() 和 __rmul__() 方法来自定义。
在 3.14 版本发生变更: 如果只有一个操作数为复数,另一参数将被转换为浮点数。
运算符 @ (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 风格的字符串格式化 一节。
modulo 运算可使用特殊的 __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_exprxor_expr:and_expr|xor_expr"^"and_expror_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_operatoror_expr)* comp_operator: "<" | ">" | "==" | ">=" | "<=" | "!=" | "is" ["not"] | ["not"] "in"
比较运算会产生布尔值: True 或 False。 自定义的 富比较方法 可能返回非布尔值。 在此情况下 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 不意味着在 a 和 c 之间进行任何比较,因此,如 x < y > z 这样的写法是完全合法的(虽然也许不太好看)。
6.10.1. 值比较¶
运算符 <, >, ==, >=, <= 和 != 将比较两个对象的值。 两个对象不要求为相同类型。
对象、值与类型 一章已说明对象都有相应的值(还有类型和标识号)。 对象值在 Python 中是一个相当抽象的概念:例如,对象值并没有一个规范的访问方法。 而且,对象值并不要求具有特定的构建方式,例如由其全部数据属性组成等。 比较运算符实现了一个特定的对象值概念。 人们可以认为这是通过实现对象比较间接地定义了对象值。
由于所有类型都是 object 的(直接或间接)的子类型,因此它们都从 object 继承了默认的比较行为。 类型可以通过实现 rich comparison methods 如 __lt__() 来自定义它们的比较行为,详情参见 基本定制。
默认的一致性比较 (== 和 !=) 是基于对象的标识号。 因此,具有相同标识号的实例一致性比较结果为相等,具有不同标识号的实例一致性比较结果为不等。 规定这种默认行为的动机是希望所有对象都应该是自反射的 (即 x is y 就意味着 x == y)。
次序比较 (<, >, <= 和 >=) 默认没有提供;如果尝试比较会引发 TypeError。 规定这种默认行为的原因是缺少与一致性比较类似的固定值。
按照默认的一致性比较行为,具有不同标识号的实例总是不相等,这可能不适合某些对象值需要有合理定义并有基于值的一致性的类型。 这样的类型需要定制自己的比较行为,实际上,许多内置类型都是这样做的。
以下列表描述了最主要内置类型的比较行为。
内置数值类型 (数字类型 --- 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的实例) 只可进行类型内部的比较,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 == xx != y和y != xx < y和y > xx <= y和y >= x比较应该是可传递的。 下列(简要的)例子显示了这一点:
x > y and y > z意味着x > zx < y and y <= z意味着x < z反向比较应该导致布尔值取反。 换句话说,下列表达式应该有相同的结果:
x == y和not x != yx < 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 是 y 的子串时 x in y 为 True。 一个等价的检测是 y.find(x) != -1。 空字符串总是被视为任何其他字符串的子串,因此 "" in "abc" 将返回 True。
对于定义了 __contains__() 方法的用户自定义类来说,如果 y.__contains__(x) 返回真值则 x in y 将返回 True,否则返回 False。
对于未定义 __contains__() 但定义了 __iter__() 的用户自定义类来说,如果在迭代 y 期间产生了值 z 使得表达式 x is z or x == z 为真值,则 x in y 将为 True。 如果在迭代期间引发了异常,则将等同于 in 引发了该异常。
最后,将会尝试旧式的迭代协议:如果一个类定义了 __getitem__(),则当且仅当存在非负整数索引号 i 使得 x is y[i] or x == y[i] 并且没有更小的索引号引发 IndexError 异常时 x in y 才为 True。 (如果引发了任何其他异常,则等同于 in 引发了该异常。)
6.10.3. 标识号比较¶
运算符 is 和 is not 用于检测对象的标识号:当且仅当 x 和 y 是同一对象时 x is y 为真。 一个对象的标识号可使用 id() 函数来确定。 x is not y 会产生相反的逻辑值。 [4]
6.11. 布尔运算¶
or_test:and_test|or_test"or"and_testand_test:not_test|and_test"and"not_testnot_test:comparison| "not"not_test
在执行布尔运算的情况下,或是当表达式被用于流程控制语句时,以下值会被解读为假值: False, None, 所有类型的数字零,以及空字符串和空容器(包括字符串、元组、列表、字典、集合与冻结集合)。 所有其他值都会被解读为真值。 用户自定义对象可通过提供 __bool__() 方法来定制其逻辑值。
运算符 not 将在其参数为假值时产生 True,否则产生 False。
表达式 x and y 首先对 x 求值;如果 x 为假则返回该值;否则对 y 求值并返回其结果值。
表达式 x or y 首先对 x 求值;如果 x 为真则返回该值;否则对 y 求值并返回其结果值。
请注意 and 和 or 都不限制其返回的值和类型必须为 False 和 True,而是返回最后被求值的操作数。 此行为是有必要的,例如假设 s 为一个当其为空时应被替换为某个默认值的字符串,表达式 s or 'foo' 将产生希望的值。 由于 not 必须创建一个新值,不论其参数为何种类型它都会返回一个布尔值(例如,not 'foo' 结果为 False 而非 ''。)
6.12. 赋值表达式¶
assignment_expression: [identifier":="]expression
赋值表达式(有时又被称为“命名表达式”或“海象表达式”)将一个 expression 赋值给一个 identifier,同时还会返回 expression 的值。
一个常见用例是在处理匹配的正则表达式的时候:
if matching := pattern.search(data):
do_something(matching)
或者是在处理分块的文件流的时候:
while chunk := file.read(9000):
process(chunk)
赋值表达式在被用作表达式语句及在被用作切片、条件表达式、lambda 表达式、关键字参数和推导式中的 if 表达式以及在 assert, with 和 assignment 语句中的子表达式时必须用圆括号括起来。 在其可使用的其他场合,圆括号则不是必须的,包括在 if 和 while 语句中。
在 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|expressionflexible_expression:assignment_expression|starred_expressionflexible_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]
除了作为列表或集合显示的一部分,包含至少一个逗号的表达式列表将生成一个元组。 元组的长度就是列表中表达式的数量。 表达式将从左至右被求值。
末尾的逗号仅在创建单条目元组,比如 1, 时才是必需的;在所有其他情况下它都是可选项。 没有末尾逗号的单独表达式不会创建一个元组,而是产生该表达式的值。 (要创建一个空元组,应使用一对内容为空的圆括号: ()。)
6.15.1. 可迭代解包¶
在表达式列表、元组、列表或集合显示中,任何表达式均可以星号(*)作为前缀。这表示 iterable unpacking。
在运行时,以星号开头的表达式必须求值为一个 可迭代对象。可迭代对象会被展开为一系列项,这些项会在解包位置被纳入新的元组、列表或集合中。
在 3.5 版本加入: 表达式列表中的可迭代对象拆包,最初由 PEP 448 提出。
在 3.11 版本加入: 一个表达式列表中的任何条目都可以带星号。 参见 PEP 646。
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 中运算符的优先顺序进行了总结,从最高优先级(最先绑定)到最低优先级(最后绑定)。 相同单元格内的运算符具有相同优先级。 除非语法显式地指明,否则运算符均为双目运算符。 相同单元格内的运算符从左至右组合的(只有幂运算符和条件表达式是从右至左组合的)。
请注意比较、成员检测和标识号检测均为相同优先级,并具有如 比较运算 一节所描述的从左至右串连特性。
运算符 |
描述 |
|---|---|
|
绑定或加圆括号的表达式,列表显示,字典显示,集合显示 |
|
抽取(包括切片)、调用、属性引用 |
await 表达式 |
|
|
乘方 [5] |
|
正,负,按位非 NOT |
|
乘,矩阵乘,除,整除,取余 [6] |
|
加和减 |
|
移位 |
|
按位与 AND |
|
按位异或 XOR |
|
按位或 OR |
比较运算,包括成员检测和标识号检测 |
|
布尔逻辑非 NOT |
|
布尔逻辑与 AND |
|
布尔逻辑或 OR |
|
|
条件表达式 |
lambda 表达式 |
|
|
赋值表达式 |
备注