8. 复合语句¶
复合语句包含(一组)其他语句;它们以某种方式影响或控制这些其他语句的执行。一般来说,复合语句跨越多行,尽管在简单的形式中,整个复合语句可能包含在一行中。
if
、while
和 for
语句实现了传统的控制流结构。try
为一组语句指定了异常处理程序和/或清理代码,而 with
语句允许在代码块周围执行初始化和最终化代码。函数和类定义在语法上也是复合语句。
复合语句由一个或多个“子句”组成。一个子句由一个头部和一个“套件”组成。特定复合语句的子句头都位于相同的缩进级别。每个子句头都以唯一标识的关键字开头,并以冒号结尾。套件是由子句控制的一组语句。套件可以是与头部同一行上的一个或多个分号分隔的简单语句,在头部冒号之后,也可以是后续行上的一个或多个缩进语句。只有后一种形式的套件才能包含嵌套的复合语句;以下是非法的,主要是因为它不清楚后面的 else
子句属于哪个 if
子句。
if test1: if test2: print(x)
还要注意,在这种上下文中,分号的绑定强度高于冒号,因此在以下示例中,要么执行所有 print()
调用,要么都不执行。
if x < y < z: print(x); print(y); print(z)
总结
compound_stmt ::=if_stmt
|while_stmt
|for_stmt
|try_stmt
|with_stmt
|match_stmt
|funcdef
|classdef
|async_with_stmt
|async_for_stmt
|async_funcdef
suite ::=stmt_list
NEWLINE | NEWLINE INDENTstatement
+ DEDENT statement ::=stmt_list
NEWLINE |compound_stmt
stmt_list ::=simple_stmt
(";"simple_stmt
)* [";"]
请注意,语句总是以 NEWLINE
结尾,可能后跟一个 DEDENT
。还要注意,可选的延续子句总是以一个不能作为语句开头的关键字开头,因此不存在歧义(Python 通过要求嵌套的 if
语句必须缩进来解决“悬空 else
”问题)。
为了清晰起见,以下各节中的语法规则的格式将每个子句放在单独的行上。
8.1. if
语句¶
if
语句用于条件执行。
if_stmt ::= "if"assignment_expression
":"suite
("elif"assignment_expression
":"suite
)* ["else" ":"suite
]
它通过逐个评估表达式来选择恰好一个套件,直到找到一个为 true 的表达式为止(有关 true 和 false 的定义,请参阅 布尔运算);然后执行该套件(并且不执行或评估 if
语句的其他部分)。如果所有表达式都为 false,则执行 else
子句(如果存在)的套件。
8.2. while
语句¶
while
语句用于只要表达式为 true 就重复执行。
while_stmt ::= "while"assignment_expression
":"suite
["else" ":"suite
]
这将重复测试表达式,如果表达式为 true,则执行第一个套件;如果表达式为 false(这可能是第一次测试时),则执行 else
子句(如果存在)的套件,并且循环终止。
在第一个套件中执行的 break
语句会终止循环,而不执行 else
子句的套件。在第一个套件中执行的 continue
语句会跳过套件的其余部分,然后返回测试表达式。
8.3. for
语句¶
for
语句用于迭代序列(例如字符串、元组或列表)或其他可迭代对象的元素。
for_stmt ::= "for"target_list
"in"starred_list
":"suite
["else" ":"suite
]
starred_list
表达式只会被评估一次;它应该产生一个 可迭代 对象。为该可迭代对象创建一个 迭代器。然后,使用赋值的标准规则(请参阅 赋值语句),将迭代器提供的第一个项分配给目标列表,并执行套件。对于迭代器提供的每个项,都会重复此操作。当迭代器耗尽时,执行 else
子句(如果存在)中的套件,并且循环终止。
在第一个套件中执行的 break
语句会终止循环,而不执行 else
子句的套件。在第一个套件中执行的 continue
语句会跳过套件的其余部分,并继续处理下一个项,或者如果没有下一个项,则继续处理 else
子句。
for 循环将赋值给目标列表中的变量。这将覆盖对这些变量的所有先前赋值,包括在 for 循环的套件中进行的赋值。
for i in range(10):
print(i)
i = 5 # this will not affect the for-loop
# because i will be overwritten with the next
# index in the range
当循环完成时,目标列表中的名称不会被删除,但如果序列为空,则循环根本不会对其进行赋值。提示:内置类型 range()
表示整数的不可变算术序列。例如,迭代 range(3)
会依次产生 0、1 和 2。
在 3.11 版本中更改: 现在表达式列表中允许使用星号元素。
8.4. try
语句¶
try
语句为一组语句指定异常处理程序和/或清理代码。
try_stmt ::=try1_stmt
|try2_stmt
|try3_stmt
try1_stmt ::= "try" ":"suite
("except" [expression
["as"identifier
]] ":"suite
)+ ["else" ":"suite
] ["finally" ":"suite
] try2_stmt ::= "try" ":"suite
("except" "*"expression
["as"identifier
] ":"suite
)+ ["else" ":"suite
] ["finally" ":"suite
] try3_stmt ::= "try" ":"suite
"finally" ":"suite
有关异常的更多信息,请参见 异常 部分,有关使用 raise
语句生成异常的信息,请参见 raise 语句 部分。
8.4.1. except
子句¶
except
子句指定一个或多个异常处理程序。当 try
子句中没有发生异常时,不执行任何异常处理程序。当 try
套件中发生异常时,将开始搜索异常处理程序。此搜索会依次检查 except
子句,直到找到一个与异常匹配的子句为止。如果没有表达式的 except
子句(如果存在),则必须放在最后;它会匹配任何异常。
对于带有表达式的 except
子句,该表达式的求值结果必须是异常类型或异常类型的元组。引发的异常与 except
子句匹配,该子句的表达式求值结果为异常对象的类或该异常对象的非虚基类,或求值结果为包含此类别的元组。
如果没有 except
子句与异常匹配,则会在周围的代码和调用堆栈中继续搜索异常处理程序。[1]
如果 except
子句的头部表达式求值时引发了异常,则会取消对处理程序的原始搜索,并开始在周围的代码和调用堆栈中搜索新的异常(就好像整个 try
语句引发了该异常一样)。
当找到匹配的 except
子句时,如果存在 as
关键字,则将异常分配给该 except
子句中 as
关键字后指定的目标,并执行 except
子句的代码块。所有 except
子句都必须有可执行的代码块。当到达此代码块的末尾时,执行将继续在整个 try
语句之后正常进行。(这意味着如果同一异常存在两个嵌套的处理程序,并且该异常发生在内部处理程序的 try
子句中,则外部处理程序不会处理该异常。)
当使用 as target
分配异常时,它会在 except
子句结束时被清除。这就像
except E as N:
foo
被翻译为
except E as N:
try:
foo
finally:
del N
这意味着必须将异常分配给不同的名称,以便在 except
子句之后能够引用它。清除异常的原因是,由于它们附带的回溯信息,它们与堆栈帧形成一个引用循环,使该帧中的所有局部变量在下一次垃圾回收发生之前都保持活动状态。
在执行 except
子句的代码块之前,该异常会存储在 sys
模块中,可以通过在 except
子句的主体中调用 sys.exception()
来访问它。当离开异常处理程序时,存储在 sys
模块中的异常会重置为其先前的值。
>>> print(sys.exception())
None
>>> try:
... raise TypeError
... except:
... print(repr(sys.exception()))
... try:
... raise ValueError
... except:
... print(repr(sys.exception()))
... print(repr(sys.exception()))
...
TypeError()
ValueError()
TypeError()
>>> print(sys.exception())
None
8.4.2. except*
子句¶
except*
子句用于处理 ExceptionGroup
。匹配的异常类型解释方式与 except
的情况相同,但在异常组的情况下,当类型与组中的某些异常匹配时,我们可以进行部分匹配。这意味着可以执行多个 except*
子句,每个子句处理异常组的一部分。每个子句最多执行一次,并处理所有匹配异常的异常组。组中的每个异常最多由一个 except*
子句处理,即第一个匹配它的子句。
>>> try:
... raise ExceptionGroup("eg",
... [ValueError(1), TypeError(2), OSError(3), OSError(4)])
... except* TypeError as e:
... print(f'caught {type(e)} with nested {e.exceptions}')
... except* OSError as e:
... print(f'caught {type(e)} with nested {e.exceptions}')
...
caught <class 'ExceptionGroup'> with nested (TypeError(2),)
caught <class 'ExceptionGroup'> with nested (OSError(3), OSError(4))
+ Exception Group Traceback (most recent call last):
| File "<stdin>", line 2, in <module>
| ExceptionGroup: eg
+-+---------------- 1 ----------------
| ValueError: 1
+------------------------------------
任何未被任何 except*
子句处理的剩余异常,以及从 except*
子句内引发的所有异常,都将在最后重新引发。如果此列表包含多个要重新引发的异常,它们将被组合成一个异常组。
如果引发的异常不是异常组,并且其类型与其中一个 except*
子句匹配,则会捕获该异常,并将其包装成一个带有空消息字符串的异常组。
>>> try:
... raise BlockingIOError
... except* BlockingIOError as e:
... print(repr(e))
...
ExceptionGroup('', (BlockingIOError()))
except*
子句必须具有匹配的表达式;它不能是 except*:
。此外,此表达式不能包含异常组类型,因为这将导致语义不明确。
在同一个 try
中不能混合使用 except
和 except*
。break
、continue
和 return
不能出现在 except*
子句中。
8.4.3. else
子句¶
如果控制流离开 try
代码块,没有引发异常,并且没有执行 return
、continue
或 break
语句,则会执行可选的 else
子句。else
子句中的异常不会由前面的 except
子句处理。
8.4.4. finally
子句¶
如果存在 finally
,则它指定一个“清理”处理程序。执行 try
子句,包括任何 except
和 else
子句。如果任何子句中发生异常且未被处理,则该异常会临时保存。执行 finally
子句。如果存在已保存的异常,则会在 finally
子句结束时重新引发该异常。如果 finally
子句引发了另一个异常,则将保存的异常设置为新异常的上下文。如果 finally
子句执行 return
、break
或 continue
语句,则会丢弃保存的异常。
>>> def f():
... try:
... 1/0
... finally:
... return 42
...
>>> f()
42
在执行 finally
子句期间,程序无法访问异常信息。
当在 try
…finally
语句的 try
代码块中执行 return
、break
或 continue
语句时,finally
子句也会在“退出时”执行。
函数的返回值由执行的最后一个 return
语句确定。由于 finally
子句始终执行,因此在 finally
子句中执行的 return
语句将始终是最后一个执行的语句。
>>> def foo():
... try:
... return 'try'
... finally:
... return 'finally'
...
>>> foo()
'finally'
在 3.8 版本中变更: 在 Python 3.8 之前,由于实现问题,continue
语句在 finally
子句中是不合法的。
8.5. with
语句¶
with
语句用于使用上下文管理器(参见 With 语句上下文管理器 部分)定义的方法来包装代码块的执行。这使得常见的 try
…except
…finally
使用模式可以被封装以便于重用。
with_stmt ::= "with" ( "("with_stmt_contents
","? ")" |with_stmt_contents
) ":"suite
with_stmt_contents ::=with_item
(","with_item
)* with_item ::=expression
["as"target
]
使用一个“项”执行 with
语句的过程如下:
计算上下文表达式(在
with_item
中给定的表达式)以获得上下文管理器。加载上下文管理器的
__enter__()
方法以供稍后使用。加载上下文管理器的
__exit__()
方法以供稍后使用。调用上下文管理器的
__enter__()
方法。如果
with
语句中包含目标,则将__enter__()
的返回值分配给它。注意
with
语句保证,如果__enter__()
方法返回时没有错误,那么__exit__()
将始终被调用。因此,如果在分配给目标列表期间发生错误,则它将被视为与在套件中发生的错误相同。请参阅下面的步骤 7。执行套件。
调用上下文管理器的
__exit__()
方法。如果异常导致套件退出,则将其类型、值和回溯作为参数传递给__exit__()
。否则,将提供三个None
参数。如果由于异常而退出套件,并且
__exit__()
方法的返回值为假,则会重新引发该异常。如果返回值为真,则会抑制该异常,并且执行将继续执行with
语句之后的语句。如果由于异常以外的任何原因退出套件,则忽略
__exit__()
的返回值,并且执行将在所采取的退出类型的正常位置继续进行。
以下代码
with EXPRESSION as TARGET:
SUITE
在语义上等同于
manager = (EXPRESSION)
enter = type(manager).__enter__
exit = type(manager).__exit__
value = enter(manager)
hit_except = False
try:
TARGET = value
SUITE
except:
hit_except = True
if not exit(manager, *sys.exc_info()):
raise
finally:
if not hit_except:
exit(manager, None, None, None)
对于多个项,上下文管理器的处理方式就像嵌套了多个 with
语句一样。
with A() as a, B() as b:
SUITE
在语义上等同于
with A() as a:
with B() as b:
SUITE
如果项被圆括号括起来,您也可以在多行中编写多项上下文管理器。例如:
with (
A() as a,
B() as b,
):
SUITE
在 3.1 版本中更改: 支持多个上下文表达式。
在 3.10 版本中更改: 支持使用分组括号将语句分成多行。
8.6. match
语句¶
添加于 3.10 版本。
match 语句用于模式匹配。语法:
match_stmt ::= 'match'subject_expr
":" NEWLINE INDENTcase_block
+ DEDENT subject_expr ::=star_named_expression
","star_named_expressions
? |named_expression
case_block ::= 'case'patterns
[guard
] ":"block
注意
本节使用单引号来表示 软关键字。
模式匹配接受一个模式作为输入(跟随 case
),以及一个主题值(跟随 match
)。该模式(可能包含子模式)与主题值进行匹配。结果是:
匹配成功或失败(也称为模式成功或失败)。
可能将匹配的值绑定到一个名称。其前提条件将在下面进一步讨论。
match
和 case
关键字是 软关键字。
8.6.1. 概述¶
以下是 match 语句的逻辑流程概述:
计算主题表达式
subject_expr
并获得结果主题值。如果主题表达式包含逗号,则使用 标准规则 构造元组。尝试将
case_block
中的每个模式与主题值进行匹配。下面将介绍成功或失败的具体规则。匹配尝试还可以绑定模式中的部分或全部独立名称。精确的模式绑定规则因模式类型而异,并在下面指定。在成功的模式匹配期间进行的名称绑定会在执行的块之后继续存在,并且可以在 match 语句之后使用。注意
在模式匹配失败期间,某些子模式可能会成功。请不要依赖于为失败的匹配所做的绑定。相反,请不要依赖于在失败的匹配之后变量保持不变。确切的行为取决于实现,并且可能有所不同。这是为了允许不同的实现添加优化而做出的有意决定。
如果模式成功,则计算相应的保护(如果存在)。在这种情况下,保证发生所有名称绑定。
如果保护评估为 true 或缺失,则执行
case_block
内的block
。否则,如上所述尝试下一个
case_block
。如果没有进一步的 case 块,则 match 语句完成。
注意
用户通常不应依赖于模式被评估。根据实现,解释器可能会缓存值或使用其他优化来跳过重复评估。
一个示例 match 语句:
>>> flag = False
>>> match (100, 200):
... case (100, 300): # Mismatch: 200 != 300
... print('Case 1')
... case (100, 200) if flag: # Successful match, but guard fails
... print('Case 2')
... case (100, y): # Matches and binds y to 200
... print(f'Case 3, y: {y}')
... case _: # Pattern not attempted
... print('Case 4, I match anything!')
...
Case 3, y: 200
在这种情况下,if flag
是一个保护。请在下一节中了解更多相关信息。
8.6.2. 保护¶
guard ::= "if" named_expression
guard
(它是 case
的一部分)必须成功才能执行 case
块内的代码。它的形式为:if
后跟一个表达式。
带有 guard
的 case
块的逻辑流程如下:
检查
case
块中的模式是否成功。如果模式失败,则不评估guard
,并且检查下一个case
块。如果模式成功,则评估
guard
。如果
guard
条件评估为 true,则选择 case 块。如果
guard
条件评估为 false,则不选择 case 块。如果
guard
在评估期间引发异常,则该异常会向上冒泡。
允许保护具有副作用,因为它们是表达式。保护评估必须从第一个 case 块开始,逐个进行到最后一个 case 块,跳过其模式并非全部成功的 case 块。(即,保护评估必须按顺序进行。)一旦选择了 case 块,保护评估就必须停止。
8.6.3. 不可反驳的 Case 块¶
不可反驳的 case 块是匹配所有 case 块。一个 match 语句最多可以有一个不可反驳的 case 块,并且它必须是最后一个。
如果 case 块没有保护并且其模式是不可反驳的,则认为该 case 块是不可反驳的。如果我们可以仅从其语法来证明它总是会成功,则认为该模式是不可反驳的。只有以下模式是不可反驳的:
8.6.4. 模式¶
注意
本节使用超出标准 EBNF 的语法表示法:
表示法
SEP.RULE+
是RULE (SEP RULE)*
的简写表示法
!RULE
是负向先行断言的简写
patterns
的顶级语法是:
patterns ::=open_sequence_pattern
|pattern
pattern ::=as_pattern
|or_pattern
closed_pattern ::= |literal_pattern
|capture_pattern
|wildcard_pattern
|value_pattern
|group_pattern
|sequence_pattern
|mapping_pattern
|class_pattern
下面的描述将包括对模式作用的“简单解释”,以作说明(感谢 Raymond Hettinger 的文档,它启发了大部分描述)。请注意,这些描述纯粹是为了说明目的,可能不反映底层的实现。此外,它们不涵盖所有有效的形式。
8.6.4.1. OR 模式¶
OR 模式是由竖线 |
分隔的两个或多个模式。 语法
or_pattern ::= "|".closed_pattern
+
只有最后一个子模式可以是不可反驳的,并且每个子模式必须绑定相同的名称集合,以避免歧义。
OR 模式依次将其每个子模式与目标值匹配,直到一个成功。然后,该 OR 模式被认为是成功的。否则,如果所有子模式均失败,则 OR 模式失败。
简单来说,P1 | P2 | ...
将尝试匹配 P1
,如果失败,它将尝试匹配 P2
,只要有任何一个成功,就会立即成功,否则失败。
8.6.4.2. AS 模式¶
AS 模式将 as
关键字左侧的 OR 模式与目标值匹配。 语法
as_pattern ::=or_pattern
"as"capture_pattern
如果 OR 模式失败,则 AS 模式失败。否则,AS 模式将目标值绑定到 as 关键字右侧的名称,并成功。capture_pattern
不能是 _
。
简单来说,P as NAME
将与 P
匹配,如果成功,则设置 NAME = <目标值>
。
8.6.4.3. 字面值模式¶
字面值模式对应于 Python 中的大多数字面值。 语法
literal_pattern ::=signed_number
|signed_number
"+" NUMBER |signed_number
"-" NUMBER |strings
| "None" | "True" | "False" signed_number ::= ["-"] NUMBER
规则 strings
和标记 NUMBER
在标准 Python 语法中定义。支持三引号字符串。支持原始字符串和字节字符串。不支持f-字符串。
形式 signed_number '+' NUMBER
和 signed_number '-' NUMBER
用于表示复数;它们要求左边是实数,右边是虚数。例如: 3 + 4j
。
简单来说,LITERAL
仅在 <目标值> == LITERAL
时才会成功。对于单例 None
、True
和 False
,将使用 is
运算符。
8.6.4.4. 捕获模式¶
捕获模式将目标值绑定到一个名称。 语法
capture_pattern ::= !'_' NAME
单个下划线 _
不是捕获模式(这就是 !'_'
所表达的)。而是将其视为通配符模式
。
在给定的模式中,一个给定的名称只能绑定一次。例如:case x, x: ...
无效,而 case [x] | x: ...
允许。
捕获模式始终成功。绑定遵循 PEP 572 中赋值表达式运算符建立的作用域规则;该名称将成为最近的包含函数作用域中的局部变量,除非有适用的 global
或 nonlocal
语句。
简单来说,NAME
始终会成功,并且会设置 NAME = <目标值>
。
8.6.4.5. 通配符模式¶
通配符模式始终成功(匹配任何内容)并且不绑定任何名称。 语法
wildcard_pattern ::= '_'
_
是任何模式中的软关键字,但仅在模式内。即使在 match
目标表达式、guard
和 case
代码块内,它也是一个标识符,与往常一样。
简单来说,_
始终会成功。
8.6.4.6. 值模式¶
值模式表示 Python 中的命名值。 语法
value_pattern ::=attr
attr ::=name_or_attr
"." NAME name_or_attr ::=attr
| NAME
使用标准 Python 名称解析规则查找模式中的点名称。如果找到的值与目标值相等(使用 ==
相等运算符),则该模式成功。
简单来说,NAME1.NAME2
仅在 <目标值> == NAME1.NAME2
时才会成功
注意
如果同一个值在同一匹配语句中出现多次,解释器可能会缓存找到的第一个值并重复使用它,而不是重复相同的查找。此缓存严格绑定到给定匹配语句的给定执行。
8.6.4.7. 分组模式¶
分组模式允许用户在模式周围添加括号,以强调预期的分组。否则,它没有额外的语法。 语法
group_pattern ::= "(" pattern
")"
简单来说,(P)
的效果与 P
相同。
8.6.4.8. 序列模式¶
序列模式包含多个子模式,以与序列元素进行匹配。该语法类似于列表或元组的解包。
sequence_pattern ::= "[" [maybe_sequence_pattern
] "]" | "(" [open_sequence_pattern
] ")" open_sequence_pattern ::=maybe_star_pattern
"," [maybe_sequence_pattern
] maybe_sequence_pattern ::= ",".maybe_star_pattern
+ ","? maybe_star_pattern ::=star_pattern
|pattern
star_pattern ::= "*" (capture_pattern
|wildcard_pattern
)
对于序列模式,使用括号还是方括号没有区别(即 (...)
与 [...]
)。
注意
用括号括起来的没有尾随逗号的单个模式(例如:(3 | 4)
)是一个分组模式。而用方括号括起来的单个模式(例如:[3 | 4]
)仍然是一个序列模式。
一个序列模式中最多可以有一个星号子模式。星号子模式可以出现在任何位置。如果不存在星号子模式,则该序列模式是固定长度的序列模式;否则,它是可变长度的序列模式。
以下是将序列模式与目标值匹配的逻辑流程
如果目标值不是[2]序列,则序列模式失败。
如果目标值是
str
、bytes
或bytearray
的实例,则序列模式失败。后续步骤取决于序列模式是固定长度还是可变长度。
如果序列模式是固定长度的
如果目标序列的长度不等于子模式的数量,则序列模式失败
序列模式中的子模式从左到右与其在目标序列中的对应项进行匹配。一旦子模式失败,匹配就会停止。如果所有子模式都成功匹配了对应的项,则序列模式成功。
否则,如果序列模式是可变长度的
如果目标序列的长度小于非星号子模式的数量,则序列模式失败。
前导的非星号子模式与固定长度序列的对应项进行匹配。
如果上一步成功,则星号子模式匹配由剩余的目标项形成的列表,不包括星号子模式之后剩余的与非星号子模式对应的项。
其余的非星号子模式与它们对应的目标项进行匹配,就像固定长度序列一样。
简单来说, [P1, P2, P3,
… , P<N>]
仅在以下所有情况发生时才匹配
检查
<目标值>
是否是序列len(目标值) == <N>
P1
匹配<目标值>[0]
(请注意,此匹配也可以绑定名称)P2
匹配<目标值>[1]
(请注意,此匹配也可以绑定名称)… 以及对应的模式/元素等等。
8.6.4.9. 映射模式¶
一个映射模式包含一个或多个键值模式。其语法类似于字典的构造。语法:
mapping_pattern ::= "{" [items_pattern
] "}" items_pattern ::= ",".key_value_pattern
+ ","? key_value_pattern ::= (literal_pattern
|value_pattern
) ":"pattern
|double_star_pattern
double_star_pattern ::= "**"capture_pattern
一个映射模式中最多可以有一个双星模式。双星模式必须是映射模式中的最后一个子模式。
映射模式中不允许重复的键。重复的字面键会引发 SyntaxError
。两个值相同的键会引发运行时 ValueError
。
以下是将映射模式与主题值进行匹配的逻辑流程:
如果主题值不是映射 [3],则映射模式匹配失败。
如果映射模式中给出的每个键都存在于主题映射中,并且每个键的模式都与主题映射的相应项匹配,则映射模式匹配成功。
如果在映射模式中检测到重复的键,则该模式被视为无效。对于重复的字面值,会引发
SyntaxError
;对于值相同的命名键,则会引发ValueError
。
注意
键值对使用映射主题的 get()
方法的双参数形式进行匹配。匹配的键值对必须已经存在于映射中,而不是通过 __missing__()
或 __getitem__()
动态创建的。
简单来说,只有当以下所有条件都成立时,{KEY1: P1, KEY2: P2, ... }
才会匹配:
检查
<subject>
是否为映射。KEY1 in <subject>
P1
匹配<subject>[KEY1]
… 以及对应的键/模式对等等。
8.6.4.10. 类模式¶
一个类模式表示一个类及其位置参数和关键字参数(如果有)。语法:
class_pattern ::=name_or_attr
"(" [pattern_arguments
","?] ")" pattern_arguments ::=positional_patterns
[","keyword_patterns
] |keyword_patterns
positional_patterns ::= ",".pattern
+ keyword_patterns ::= ",".keyword_pattern
+ keyword_pattern ::= NAME "="pattern
同一个关键字不应在类模式中重复。
以下是将类模式与主题值进行匹配的逻辑流程:
如果主题值不是
name_or_attr
的实例(通过isinstance()
测试),则类模式匹配失败。如果没有模式参数,则模式匹配成功。否则,后续步骤取决于是否存在关键字或位置参数模式。
对于一些内置类型(如下所述),接受单个位置子模式,该子模式将匹配整个主题;对于这些类型,关键字模式的工作方式与其他类型相同。
如果只存在关键字模式,则它们按以下方式逐个处理:
I. 关键字作为主题的属性进行查找。
如果此操作引发了
AttributeError
之外的异常,则该异常会向上冒泡。如果此操作引发了
AttributeError
,则类模式匹配失败。否则,与关键字模式关联的子模式会与主题的属性值进行匹配。如果此操作失败,则类模式匹配失败;如果此操作成功,则匹配继续进行到下一个关键字。
II. 如果所有关键字模式都匹配成功,则类模式匹配成功。
如果存在任何位置模式,则在使用类
name_or_attr
上的__match_args__
属性匹配之前,将它们转换为关键字模式。I. 调用等效于
getattr(cls, "__match_args__", ())
的代码。- II. 一旦所有位置模式都转换为关键字模式,
匹配的进行方式就如同只有关键字模式一样。
对于以下内置类型,位置子模式的处理方式不同:
这些类接受单个位置参数,并且此处的模式会与整个对象(而不是属性)进行匹配。例如,
int(0|1)
匹配值0
,但不匹配值0.0
。
简单来说,只有当以下条件成立时,CLS(P1, attr=P2)
才会匹配:
isinstance(<subject>, CLS)
使用
CLS.__match_args__
将P1
转换为关键字模式。对于每个关键字参数
attr=P2
:hasattr(<subject>, "attr")
P2
匹配<subject>.attr
… 以及对应的关键字参数/模式对等等。
8.7. 函数定义¶
函数定义定义一个用户定义的函数对象(请参阅 标准类型层级结构 部分)
funcdef ::= [decorators
] "def"funcname
[type_params
] "(" [parameter_list
] ")" ["->"expression
] ":"suite
decorators ::=decorator
+ decorator ::= "@"assignment_expression
NEWLINE parameter_list ::=defparameter
(","defparameter
)* "," "/" ["," [parameter_list_no_posonly
]] |parameter_list_no_posonly
parameter_list_no_posonly ::=defparameter
(","defparameter
)* ["," [parameter_list_starargs
]] |parameter_list_starargs
parameter_list_starargs ::= "*" [star_parameter
] (","defparameter
)* ["," ["**"parameter
[","]]] | "**"parameter
[","] parameter ::=identifier
[":"expression
] star_parameter ::=identifier
[":" ["*"]expression
] defparameter ::=parameter
["="expression
] funcname ::=identifier
函数定义是一个可执行语句。它的执行将当前局部命名空间中的函数名称绑定到一个函数对象(函数的可执行代码的包装器)。该函数对象包含对当前全局命名空间的引用,作为调用该函数时要使用的全局命名空间。
函数定义不执行函数体;函数体只有在调用函数时才会被执行。[4]
函数定义可以被一个或多个 装饰器 表达式包裹。装饰器表达式在定义函数时进行求值,其作用域包含函数定义。结果必须是可调用的,并以函数对象作为唯一参数进行调用。返回的值将绑定到函数名称,而不是函数对象。多个装饰器以嵌套方式应用。例如,以下代码
@f1(arg)
@f2
def func(): pass
大致相当于
def func(): pass
func = f1(arg)(f2(func))
除了原始函数不会临时绑定到名称 func
。
3.9 版本更改: 函数可以使用任何有效的assignment_expression
进行装饰。 以前,语法限制要严格得多; 请参阅PEP 614了解详细信息。
可以在函数名称和参数列表的左括号之间使用方括号指定类型参数列表。 这会向静态类型检查器表明该函数是泛型函数。 在运行时,可以从函数的__type_params__
属性检索类型参数。 有关更多信息,请参见泛型函数。
3.12 版本新增: 类型参数列表是 Python 3.12 中的新功能。
当一个或多个形参具有 *形参* =
*表达式* 的形式时,该函数被称为具有“默认形参值”。 对于具有默认值的形参,在调用时可以省略相应的实参,在这种情况下,将替换为该形参的默认值。 如果一个形参具有默认值,那么直到 “*
” 之前的所有后续形参也必须具有默认值——这是一个语法限制,而没有在语法中表达出来。
默认形参值在函数定义执行时从左到右进行求值。 这意味着表达式在定义函数时求值一次,并且每次调用都使用相同的“预计算”值。 当默认形参值是可变对象(例如列表或字典)时,这一点尤其重要:如果函数修改了该对象(例如,通过将项目追加到列表),则实际上会修改默认形参值。 这通常不是预期的结果。 一种解决方法是使用 None
作为默认值,并在函数体中显式地对其进行测试,例如:
def whats_on_the_telly(penguin=None):
if penguin is None:
penguin = []
penguin.append("property of the zoo")
return penguin
函数调用语义在 调用 一节中进行了更详细的描述。 函数调用始终会从位置实参、关键字实参或默认值中为形参列表中提到的所有形参赋值。 如果存在 “*identifier
” 形式,则将其初始化为接收任何多余位置形参的元组,默认为空元组。 如果存在 “**identifier
” 形式,则将其初始化为接收任何多余关键字实参的新排序映射,默认为相同类型的新空映射。“*
” 或 “*identifier
” 之后的形参是仅限关键字形参,只能通过关键字实参传递。“/
” 之前的形参是仅限位置形参,只能通过位置实参传递。
3.8 版本更改: 可以使用 /
函数形参语法来指示仅限位置形参。 请参阅PEP 570了解详细信息。
形参可以具有 “: 表达式
” 形式的注解,该注解紧随形参名称之后。 任何形参都可以具有注解,甚至包括 *identifier
或 **identifier
形式的形参。(作为特例,*identifier
形式的形参可以具有 “: *表达式
” 的注解。) 函数可以在形参列表之后具有 “-> 表达式
” 形式的“返回”注解。 这些注解可以是任何有效的 Python 表达式。 注解的存在不会改变函数的语义。 注解值在函数对象的 __annotations__
属性中作为键入形参名称的字典的值提供。 如果使用了来自 __future__
的 annotations
导入,则会在运行时将注解保留为字符串,从而实现延迟求值。 否则,它们会在执行函数定义时求值。 在这种情况下,注解的求值顺序可能与它们在源代码中出现的顺序不同。
3.11 版本更改: *identifier
形式的形参可以具有 “: *表达式
” 的注解。 请参阅PEP 646。
还可以创建匿名函数(未绑定到名称的函数),以便在表达式中立即使用。 这使用 lambda 表达式,在 Lambdas 一节中进行了描述。 请注意,lambda 表达式只是简化函数定义的简写形式; 在 “def
” 语句中定义的函数可以像 lambda 表达式定义的函数一样被传递或赋值给另一个名称。 “def
” 形式实际上更强大,因为它允许执行多个语句和注解。
程序员的注意事项: 函数是第一类对象。 在函数定义内部执行的 “def
” 语句定义了一个局部函数,该函数可以返回或传递。 嵌套函数中使用的自由变量可以访问包含 def 的函数的局部变量。 有关详细信息,请参见 命名与绑定 一节。
8.8. 类定义¶
类定义定义一个类对象(请参阅 标准类型层级结构 一节)
classdef ::= [decorators
] "class"classname
[type_params
] [inheritance
] ":"suite
inheritance ::= "(" [argument_list
] ")" classname ::=identifier
类定义是可执行语句。 继承列表通常给出基类的列表(有关更高级的用法,请参阅 元类),因此列表中的每个项目都应求值为允许子类化的类对象。 没有继承列表的类默认从基类 object
继承; 因此,
class Foo:
pass
等同于
class Foo(object):
pass
然后,在新的执行帧中(请参阅 命名与绑定)使用新创建的局部命名空间和原始全局命名空间执行类的套件。(通常,套件主要包含函数定义。)当类的套件完成执行时,其执行帧将被丢弃,但其局部命名空间将被保存。[5] 然后,使用基类的继承列表和保存的局部命名空间(用于属性字典)创建类对象。类名在原始局部命名空间中绑定到该类对象。
在类主体中定义属性的顺序将保留在新类的 __dict__
中。 请注意,这仅在创建类之后且仅适用于使用定义语法定义的类才是可靠的。
可以使用元类对类的创建进行高度自定义。
类也可以被装饰:就像装饰函数时一样,
@f1(arg)
@f2
class Foo: pass
大致相当于
class Foo: pass
Foo = f1(arg)(f2(Foo))
装饰器表达式的求值规则与函数装饰器的求值规则相同。 然后将结果绑定到类名。
3.9 版本更改: 类可以使用任何有效的 assignment_expression
进行装饰。 以前,语法限制要严格得多; 请参阅PEP 614了解详细信息。
可以在类名称之后立即使用方括号指定类型参数列表。 这会向静态类型检查器表明该类是泛型类。 在运行时,可以从类的 __type_params__
属性检索类型参数。 有关更多信息,请参见 泛型类。
3.12 版本新增: 类型参数列表是 Python 3.12 中的新功能。
程序员注意事项:在类定义中定义的变量是类属性;它们由实例共享。实例属性可以在方法中使用 self.name = value
进行设置。类属性和实例属性都可以通过 “self.name
” 这种表示法进行访问,当以这种方式访问时,实例属性会隐藏同名的类属性。类属性可以用作实例属性的默认值,但使用可变值可能会导致意外的结果。描述器可用于创建具有不同实现细节的实例变量。
8.9. 协程¶
在版本 3.5 中添加。
8.9.1. 协程函数定义¶
async_funcdef ::= [decorators
] "async" "def"funcname
"(" [parameter_list
] ")" ["->"expression
] ":"suite
Python 协程的执行可以在多个点暂停和恢复(请参阅协程)。await
表达式、async for
和 async with
只能在协程函数的主体中使用。
使用 async def
语法定义的函数始终是协程函数,即使它们不包含 await
或 async
关键字。
在协程函数的主体中使用 yield from
表达式是 SyntaxError
错误。
一个协程函数的例子
async def func(param1, param2):
do_stuff()
await some_coroutine()
在 3.7 版本中更改:await
和 async
现在是关键字;以前它们只在协程函数的主体中被视为关键字。
8.9.2. async for
语句¶
async_for_stmt ::= "async" for_stmt
异步可迭代对象提供了一个 __aiter__
方法,该方法直接返回一个异步迭代器,该迭代器可以在其 __anext__
方法中调用异步代码。
async for
语句允许方便地迭代异步可迭代对象。
以下代码
async for TARGET in ITER:
SUITE
else:
SUITE2
语义上等同于
iter = (ITER)
iter = type(iter).__aiter__(iter)
running = True
while running:
try:
TARGET = await type(iter).__anext__(iter)
except StopAsyncIteration:
running = False
else:
SUITE
else:
SUITE2
另请参阅 __aiter__()
和 __anext__()
以获取详细信息。
在协程函数的主体外部使用 async for
语句是 SyntaxError
错误。
8.9.3. async with
语句¶
async_with_stmt ::= "async" with_stmt
异步上下文管理器是一个上下文管理器,它能够在它的 enter 和 exit 方法中暂停执行。
以下代码
async with EXPRESSION as TARGET:
SUITE
在语义上等同于
manager = (EXPRESSION)
aenter = type(manager).__aenter__
aexit = type(manager).__aexit__
value = await aenter(manager)
hit_except = False
try:
TARGET = value
SUITE
except:
hit_except = True
if not await aexit(manager, *sys.exc_info()):
raise
finally:
if not hit_except:
await aexit(manager, None, None, None)
另请参阅 __aenter__()
和 __aexit__()
以获取详细信息。
在协程函数的主体外部使用 async with
语句是 SyntaxError
错误。
另请参阅
- PEP 492 - 使用 async 和 await 语法的协程
该提案使协程成为 Python 中一个适当的独立概念,并添加了支持语法。
8.10. 类型参数列表¶
在版本 3.12 中添加。
在 3.13 版本中更改:添加了对默认值的支持(请参阅 PEP 696)。
type_params ::= "["type_param
(","type_param
)* "]" type_param ::=typevar
|typevartuple
|paramspec
typevar ::=identifier
(":"expression
)? ("="expression
)? typevartuple ::= "*"identifier
("="expression
)? paramspec ::= "**"identifier
("="expression
)?
def max[T](args: list[T]) -> T:
...
async def amax[T](args: list[T]) -> T:
...
class Bag[T]:
def __iter__(self) -> Iterator[T]:
...
def add(self, arg: T) -> None:
...
type ListOrSet[T] = list[T] | set[T]
从语义上讲,这表明函数、类或类型别名是关于类型变量的泛型。此信息主要由静态类型检查器使用,在运行时,泛型对象的行为很像它们的非泛型对应物。
类型参数在方括号 ([]
) 中声明,紧跟在函数、类或类型别名的名称之后。类型参数在泛型对象的范围内是可访问的,但在其他地方不可访问。因此,在声明 def func[T](): pass
之后,名称 T
在模块范围内不可用。下面,将更精确地描述泛型对象的语义。类型参数的作用域使用一个特殊函数(技术上是一个注解作用域)来建模,该函数封装了泛型对象的创建。
泛型函数、类和类型别名具有一个 __type_params__
属性,该属性列出它们的类型参数。
类型参数有三种类型
由一个普通名称引入的
typing.TypeVar
(例如,T
)。从语义上讲,这表示类型检查器的单个类型。由一个带单星号前缀的名称引入的
typing.TypeVarTuple
(例如,*Ts
)。从语义上讲,这代表任意数量的类型组成的元组。由一个带双星号前缀的名称引入的
typing.ParamSpec
(例如,**P
)。从语义上讲,这表示可调用对象的参数。
typing.TypeVar
声明可以使用冒号 (:
) 后跟表达式来定义边界和约束。冒号后的单个表达式表示边界(例如 T: int
)。从语义上讲,这意味着 typing.TypeVar
只能表示此边界的子类型。冒号后带括号的表达式元组表示一组约束(例如 T: (str, bytes)
)。元组的每个成员都应该是一个类型(同样,这在运行时不会强制执行)。受约束的类型变量只能采用约束列表中的一个类型。
对于使用类型参数列表语法声明的 typing.TypeVar
,边界和约束不会在创建泛型对象时进行评估,而仅在通过属性 __bound__
和 __constraints__
显式访问值时进行评估。为此,边界或约束会在单独的注解作用域中进行评估。
typing.TypeVarTuple
和 typing.ParamSpec
不能有边界或约束。
所有三种类型的类型参数也可以具有一个默认值,当未显式提供类型参数时使用该默认值。这是通过附加一个等号 (=
) 后跟一个表达式来添加的。与类型变量的边界和约束一样,默认值不会在创建对象时进行评估,而仅在访问类型参数的 __default__
属性时进行评估。为此,默认值在单独的注解作用域中进行评估。如果未为类型参数指定默认值,则 __default__
属性将设置为特殊的哨兵对象 typing.NoDefault
。
以下示例指示允许的类型参数声明的完整集合
def overly_generic[
SimpleTypeVar,
TypeVarWithDefault = int,
TypeVarWithBound: int,
TypeVarWithConstraints: (str, bytes),
*SimpleTypeVarTuple = (int, float),
**SimpleParamSpec = (str, bytearray),
](
a: SimpleTypeVar,
b: TypeVarWithDefault,
c: TypeVarWithBound,
d: Callable[SimpleParamSpec, TypeVarWithConstraints],
*e: SimpleTypeVarTuple,
): ...
8.10.1. 泛型函数¶
泛型函数声明如下:
def func[T](arg: T): ...
此语法等价于:
annotation-def TYPE_PARAMS_OF_func():
T = typing.TypeVar("T")
def func(arg: T): ...
func.__type_params__ = (T,)
return func
func = TYPE_PARAMS_OF_func()
此处 annotation-def
表示一个注解作用域,它实际上在运行时不会绑定到任何名称。(在转换中还采取了另外一种自由:该语法不通过对 typing
模块的属性访问,而是直接创建一个 typing.TypeVar
的实例。)
泛型函数的注解在用于声明类型参数的注解作用域内进行评估,但函数的默认值和装饰器则不会。
以下示例说明了这些情况以及类型参数的其他形式的作用域规则:
@decorator
def func[T: int, *Ts, **P](*args: *Ts, arg: Callable[P, T] = some_default):
...
DEFAULT_OF_arg = some_default
annotation-def TYPE_PARAMS_OF_func():
annotation-def BOUND_OF_T():
return int
# In reality, BOUND_OF_T() is evaluated only on demand.
T = typing.TypeVar("T", bound=BOUND_OF_T())
Ts = typing.TypeVarTuple("Ts")
P = typing.ParamSpec("P")
def func(*args: *Ts, arg: Callable[P, T] = DEFAULT_OF_arg):
...
func.__type_params__ = (T, Ts, P)
return func
func = decorator(TYPE_PARAMS_OF_func())
诸如 DEFAULT_OF_arg
之类的以大写字母开头的名称实际上在运行时不会绑定。
8.10.2. 泛型类¶
泛型类声明如下:
class Bag[T]: ...
此语法等价于:
annotation-def TYPE_PARAMS_OF_Bag():
T = typing.TypeVar("T")
class Bag(typing.Generic[T]):
__type_params__ = (T,)
...
return Bag
Bag = TYPE_PARAMS_OF_Bag()
这里,annotation-def
(不是真正的关键字)表示一个 注解作用域,并且名称 TYPE_PARAMS_OF_Bag
实际上在运行时不会绑定。
泛型类隐式地继承自 typing.Generic
。泛型类的基类和关键字参数在类型参数的类型作用域内进行评估,而装饰器则在该作用域之外进行评估。这个例子说明了这一点:
@decorator
class Bag(Base[T], arg=T): ...
这等价于:
annotation-def TYPE_PARAMS_OF_Bag():
T = typing.TypeVar("T")
class Bag(Base[T], typing.Generic[T], arg=T):
__type_params__ = (T,)
...
return Bag
Bag = decorator(TYPE_PARAMS_OF_Bag())
8.10.3. 泛型类型别名¶
type
语句也可以用来创建一个泛型类型别名:
type ListOrSet[T] = list[T] | set[T]
除了值的延迟求值外,这等价于:
annotation-def TYPE_PARAMS_OF_ListOrSet():
T = typing.TypeVar("T")
annotation-def VALUE_OF_ListOrSet():
return list[T] | set[T]
# In reality, the value is lazily evaluated
return typing.TypeAliasType("ListOrSet", VALUE_OF_ListOrSet(), type_params=(T,))
ListOrSet = TYPE_PARAMS_OF_ListOrSet()
这里,annotation-def
(不是真正的关键字)表示一个 注解作用域。诸如 TYPE_PARAMS_OF_ListOrSet
之类的大写字母开头的名称实际上在运行时不会绑定。
脚注