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
。还要注意,可选的延续子句总是以一个不能作为语句开头的关键字开始,因此没有歧义(“悬挂 else
” 问题在 Python 中通过要求嵌套的 if
语句进行缩进来解决)。
为了清晰起见,以下章节中的语法规则格式将每个子句放在单独的一行。
8.1. if
语句¶
if
语句用于条件执行:
if_stmt: "if"assignment_expression
":"suite
("elif"assignment_expression
":"suite
)* ["else" ":"suite
]
它通过逐一求值表达式,直到找到一个为真的表达式(关于真和假的定义,请参见 布尔运算),然后选择其中一个代码块来执行;然后该代码块被执行(并且 if
语句的其他部分不会被执行或求值)。如果所有表达式都为假,则执行 else
子句的代码块(如果存在)。
8.2. while
语句¶
while
语句用于在表达式为真的情况下重复执行:
while_stmt: "while"assignment_expression
":"suite
["else" ":"suite
]
它会重复测试表达式,如果为真,则执行第一个代码块;如果表达式为假(可能在第一次测试时就为假),则执行 else
子句的代码块(如果存在),然后循环终止。
在第一个代码块中执行的 break
语句会终止循环,而不会执行 else
子句的代码块。在第一个代码块中执行的 continue
语句会跳过代码块的其余部分,并返回到测试表达式的地方。
8.3. for
语句¶
for
语句用于迭代一个序列(例如字符串、元组或列表)或其他可迭代对象的元素:
for_stmt: "for"target_list
"in"starred_expression_list
":"suite
["else" ":"suite
]
starred_expression_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 语句 部分。
在 3.14 版本发生变更: 支持在使用多个异常类型时可选地省略分组括号。请参阅 PEP 758。
8.4.1. except
子句¶
except
子句指定一个或多个异常处理器。当 try
子句中没有发生异常时,不会执行任何异常处理器。当 try
代码块中发生异常时,会开始搜索异常处理器。这个搜索会依次检查 except
子句,直到找到一个与异常匹配的子句。一个没有表达式的 except
子句(如果存在)必须是最后一个;它会匹配任何异常。
对于带有表达式的 except
子句,该表达式必须求值为一个异常类型或一个异常类型的元组。如果提供了多个异常类型且未使用 as
子句,可以省略括号。抛出的异常如果是一个类或是异常对象的 非虚拟基类,或者是一个包含该类的元组,那么这个异常就匹配这个 except
子句,该子句的表达式求值结果就是这个类或元组。
如果没有 except
子句匹配异常,异常处理器的搜索将在周围的代码和调用栈中继续进行。[1]
如果在 except
子句的句头中对表达式求值时引发了异常,那么对原处理器的搜索将被取消,并在周围代码和调用栈中开始对新异常的搜索(这被视为整个 try
语句引发了该异常)。
当找到匹配的 except
子句时,如果该 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
模块中,可以通过调用 sys.exception()
在 except
子句的主体内访问它。离开异常处理器时,存储在 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*
子句指定一个或多个异常组(BaseExceptionGroup
实例)的处理器。一个 try
语句可以有 except
或 except*
子句,但不能两者都有。在 except*
的情况下,用于匹配的异常类型是强制性的,所以 except*:
是一个语法错误。类型的解释与 except
的情况相同,但匹配是在正在处理的异常组中包含的异常上进行的。如果匹配的类型是 BaseExceptionGroup
的子类,会引发一个 TypeError
,因为这会导致语义模糊。
当在 try 块中引发一个异常组时,每个 except*
子句会将其(见 split()
)拆分为匹配和不匹配的异常子组。如果匹配的子组非空,它就成为被处理的异常(即 sys.exception()
返回的值),并被赋给 except*
子句的目标(如果存在)。然后,except*
子句的主体被执行。如果不匹配的子组非空,它会被下一个 except*
以同样的方式处理。这个过程会一直持续,直到异常组中的所有异常都被匹配,或者最后一个 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 "<doctest default[0]>", line 2, in <module>
| raise ExceptionGroup("eg",
| [ValueError(1), TypeError(2), OSError(3), OSError(4)])
| ExceptionGroup: eg (1 sub-exception)
+-+---------------- 1 ----------------
| ValueError: 1
+------------------------------------
如果从 try
块中引发的异常不是一个异常组,并且它的类型匹配某个 except*
子句,它会被捕获并用一个空消息字符串包装成一个异常组。这确保了目标 e
的类型始终是 BaseExceptionGroup
:
>>> try:
... raise BlockingIOError
... except* BlockingIOError as e:
... print(repr(e))
...
ExceptionGroup('', (BlockingIOError()))
8.4.3. else
子句¶
可选的 else
子句在控制流离开 try
代码块,且没有引发异常,也没有执行 return
、continue
或 break
语句时执行。else
子句中的异常不会被前面的 except
子句处理。
8.4.4. finally
子句¶
如果存在 finally
,它指定一个“清理”处理器。try
子句会被执行,包括任何 except
和 else
子句。如果在任何子句中发生异常且未被处理,该异常会被临时保存。finally
子句会被执行。如果有一个已保存的异常,它会在 finally
子句的末尾被重新引发。如果 finally
子句引发了另一个异常,已保存的异常会被设置为新异常的上下文。如果 finally
子句执行了 return
、break
或 continue
语句,已保存的异常会被丢弃。例如,这个函数返回 42。
def f():
try:
1/0
finally:
return 42
在执行 finally
子句期间,异常信息对程序是不可用的。
当在 try
…finally
语句的 try
代码块中执行 return
、break
或 continue
语句时,finally
子句也会在“退出时”执行。
函数的返回值由最后执行的 return
语句确定。由于 finally
子句总是执行,因此在 finally
子句中执行的 return
语句将始终是最后执行的那个。下面的函数返回 'finally'。
def foo():
try:
return 'try'
finally:
return 'finally'
在 3.8 版本发生变更: 在 Python 3.8 之前,由于实现上的问题,continue
语句在 finally
子句中是无效的。
在 3.14 版本发生变更: 当 return
、break
或 continue
出现在 finally
块中时,编译器会发出一个 SyntaxWarning
(见 PEP 765)。
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 语句之后使用。备注
在失败的模式匹配期间,某些子模式可能会成功。不要依赖于为失败的匹配进行的绑定。反之,也不要依赖于变量在失败的匹配后保持不变。确切的行为取决于实现,可能会有所不同。这是一个有意的决定,旨在允许不同的实现添加优化。
如果模式成功,则对相应的守护条件(如果存在)进行求值。在这种情况下,可以保证所有名称绑定都已经发生。
如果守护条件求值为真或缺失,则执行
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
条件求值为真,则选择该 case 块。如果
guard
条件求值为假,则不选择该 case 块。如果
guard
在求值期间引发异常,则异常会向上冒泡。
守护条件允许有副作用,因为它们是表达式。守护条件的求值必须从第一个到最后一个 case 块,一次一个地进行,跳过其模式并非全部成功的 case 块。(即,守护条件的求值必须按顺序进行。)一旦一个 case 块被选中,守护条件的求值必须停止。
8.6.3. 不可反驳的 case 块¶
一个不可反驳的 case 块是一个匹配所有的 case 块。一个 match 语句最多可以有一个不可反驳的 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 = <subject>
。
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-字符串和t-字符串。
signed_number '+' NUMBER
和 signed_number '-' NUMBER
这两种形式用于表示复数;它们要求左边是实数,右边是虚数。例如 3 + 4j
。
通俗地讲,LITERAL
只有在 <subject> == LITERAL
时才会成功。对于单例 None
、True
和 False
,会使用 is
运算符。
8.6.4.4. 捕获模式¶
捕获模式将主题值绑定到一个名称。语法:
capture_pattern: !'_' NAME
单个下划线 _
不是捕获模式(这正是 !'_'
所表达的)。它被视为一个wildcard_pattern
。
在给定的模式中,一个给定的名称只能被绑定一次。例如,case x, x: ...
是无效的,而 case [x] | x: ...
是允许的。
捕获模式总是成功。绑定遵循 PEP 572 中赋值表达式运算符建立的作用域规则;除非有适用的 global
或 nonlocal
语句,否则该名称成为最近的包含函数作用域中的局部变量。
通俗地讲,NAME
将总是成功,并且它将设置 NAME = <subject>
。
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
只有在 <subject> == NAME1.NAME2
时才会成功。
备注
如果同一个值在同一个 match 语句中多次出现,解释器可能会缓存找到的第一个值并重用它,而不是重复相同的查找。这个缓存严格绑定于给定的 match 语句的某一次执行。
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
)
对于序列模式,使用圆括号或方括号没有区别(即 (...)
vs [...]
)。
备注
一个被圆括号包围且没有尾随逗号的单个模式(例如 (3 | 4)
)是一个分组模式。而一个被方括号包围的单个模式(例如 [3 | 4]
)仍然是一个序列模式。
一个序列模式中最多可以有一个星号子模式。星号子模式可以出现在任何位置。如果没有星号子模式,该序列模式是固定长度的序列模式;否则它是可变长度的序列模式。
以下是将序列模式与主题值进行匹配的逻辑流程:
如果主题值不是一个序列[2],则序列模式失败。
如果主题值是
str
、bytes
或bytearray
的实例,则序列模式失败。后续步骤取决于序列模式是固定长度还是可变长度。
如果序列模式是固定长度的:
如果主题序列的长度不等于子模式的数量,则序列模式失败。
序列模式中的子模式会从左到右与其在主题序列中的对应项进行匹配。只要有一个子模式失败,匹配就会停止。如果所有子模式都成功匹配其对应项,则序列模式成功。
否则,如果序列模式是可变长度的:
如果主题序列的长度小于非星号子模式的数量,则序列模式失败。
前导的非星号子模式会像固定长度序列一样与其对应项进行匹配。
如果前一步成功,星号子模式会匹配一个由剩余的主题项组成的列表,不包括星号子模式后面对应的非星号子模式的剩余项。
剩余的非星号子模式会像固定长度序列一样与其对应的主题项进行匹配。
通俗地讲,[P1, P2, P3,
… , P<N>]
只有在以下所有情况都发生时才匹配:
检查
<subject>
是否为序列len(subject) == <N>
P1
匹配<subject>[0]
(注意这个匹配也可以绑定名称)P2
匹配<subject>[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_star_kwargs
]] | "*" (","defparameter
)+ ["," [parameter_star_kwargs
]] |parameter_star_kwargs
parameter_star_kwargs: "**"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 中的新功能。
当一个或多个 形参 具有 parameter =
expression 这样的形式时,该函数就被称为具有“默认形参值”。对于有默认值的形参,在调用中对应的 实参 可以被省略,此时将使用形参的默认值。如果一个形参有默认值,那么在它之后直到 “*
” 为止的所有形参都必须有默认值 —— 这是一个句法限制,并未在语法中明确表达。
默认形参值在函数定义被执行时,会从左至右地进行求值。 这意味着表达式只在函数定义时被求值一次,并且每次调用都使用同一个“预计算”的值。当默认形参值是一个可变对象(例如列表或字典)时,理解这一点尤为重要:如果函数修改了该对象(例如通过向列表附加一个项),那么默认形参值实际上也被修改了。这通常不是我们所期望的。一种解决方法是使用 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。
形参可以在形参名称后带有 “: expression
” 形式的 标注。任何形参都可以有标注,即使是 *identifier
或 **identifier
这种形式的形参也不例外。(作为一个特例,*identifier
形式的形参可以有 “: *expression
” 形式的标注。)函数可以在形参列表后带有 “-> expression
” 形式的“返回”标注。这些标注可以是任何有效的 Python 表达式。标注的存在不会改变函数的语义。有关标注的更多信息,请参阅 标注。
在 3.11 版更改: “*identifier
” 形式的形参可以有 “: *expression
” 形式的标注。参见 PEP 646。
也可以创建匿名函数(未绑定到名称的函数),以便在表达式中直接使用。这需要使用 lambda 表达式,在 Lambda 表达式 一节中有描述。请注意,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
s,其上界和约束在泛型对象创建时不会被求值,而只有在通过 __bound__
和 __constraints__
属性显式访问其值时才会被求值。为实现这一点,上界或约束会在一个单独的标注作用域中被求值。
typing.TypeVarTuple
s 和 typing.ParamSpec
s 不能有上界或约束。
所有三种类型的类型形参都可以有一个*默认值*,当类型形参没有被显式提供时会使用该值。通过追加一个等号(=
)和一个表达式来添加。与类型变量的上界和约束一样,默认值在对象创建时不会被求值,而只在访问类型形参的 __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
这样的大写名称在运行时实际上并未被绑定。
8.11. 标注¶
在 3.14 版更改: 标注现在默认是延迟求值的。
变量和函数形参可以带有标注,通过在名称后添加冒号和一个表达式来创建:
x: annotation = 1
def f(param: annotation): ...
函数也可以在箭头后带有一个返回标注:
def f() -> annotation: ...
标注通常用于类型提示,但这并非由语言强制执行,并且通常标注可以包含任意表达式。标注的存在不会改变代码的运行时语义,除非使用了某种机制来内省和使用这些标注(例如 dataclasses
或 functools.singledispatch()
)。
默认情况下,标注会在一个标注作用域内延迟求值。这意味着它们在包含标注的代码被求值时不会被求值。相反,解释器会保存信息,以便在需要时稍后求值该标注。annotationlib
模块提供了用于求值标注的工具。
如果存在future 语句 from __future__ import annotations
,则所有标注都会被存储为字符串:
>>> from __future__ import annotations
>>> def f(param: annotation): ...
>>> f.__annotations__
{'param': 'annotation'}
这个 future 语句将在未来版本的 Python 中被弃用并移除,但不会早于 Python 3.13 的生命周期结束(参见 PEP 749)。当使用它时,像 annotationlib.get_annotations()
和 typing.get_type_hints()
这样的内省工具在运行时解析标注的可能性会降低。
脚注