8. 复合语句

复合语句包含(一组)其他语句;它们以某种方式影响或控制这些其他语句的执行。一般来说,复合语句跨越多行,尽管在简单的形式中,整个复合语句可能包含在一行中。

ifwhilefor 语句实现了传统的控制流结构。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 INDENT statement+ 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 中不能混合使用 exceptexcept*breakcontinuereturn 不能出现在 except* 子句中。

8.4.3. else 子句

如果控制流离开 try 代码块,没有引发异常,并且没有执行 returncontinuebreak 语句,则会执行可选的 else 子句。else 子句中的异常不会由前面的 except 子句处理。

8.4.4. finally 子句

如果存在 finally,则它指定一个“清理”处理程序。执行 try 子句,包括任何 exceptelse 子句。如果任何子句中发生异常且未被处理,则该异常会临时保存。执行 finally 子句。如果存在已保存的异常,则会在 finally 子句结束时重新引发该异常。如果 finally 子句引发了另一个异常,则将保存的异常设置为新异常的上下文。如果 finally 子句执行 returnbreakcontinue 语句,则会丢弃保存的异常。

>>> def f():
...     try:
...         1/0
...     finally:
...         return 42
...
>>> f()
42

在执行 finally 子句期间,程序无法访问异常信息。

当在 tryfinally 语句的 try 代码块中执行 returnbreakcontinue 语句时,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 语句上下文管理器 部分)定义的方法来包装代码块的执行。这使得常见的 tryexceptfinally 使用模式可以被封装以便于重用。

with_stmt          ::=  "with" ( "(" with_stmt_contents ","? ")" | with_stmt_contents ) ":" suite
with_stmt_contents ::=  with_item ("," with_item)*
with_item          ::=  expression ["as" target]

使用一个“项”执行 with 语句的过程如下:

  1. 计算上下文表达式(在 with_item 中给定的表达式)以获得上下文管理器。

  2. 加载上下文管理器的 __enter__() 方法以供稍后使用。

  3. 加载上下文管理器的 __exit__() 方法以供稍后使用。

  4. 调用上下文管理器的 __enter__() 方法。

  5. 如果 with 语句中包含目标,则将 __enter__() 的返回值分配给它。

    注意

    with 语句保证,如果 __enter__() 方法返回时没有错误,那么 __exit__() 将始终被调用。因此,如果在分配给目标列表期间发生错误,则它将被视为与在套件中发生的错误相同。请参阅下面的步骤 7。

  6. 执行套件。

  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 版本中更改: 支持使用分组括号将语句分成多行。

另请参阅

PEP 343 - “with” 语句

Python with 语句的规范、背景和示例。

8.6. match 语句

添加于 3.10 版本。

match 语句用于模式匹配。语法:

match_stmt   ::=  'match' subject_expr ":" NEWLINE INDENT case_block+ DEDENT
subject_expr ::=  star_named_expression "," star_named_expressions?
                  | named_expression
case_block   ::=  'case' patterns [guard] ":" block

注意

本节使用单引号来表示 软关键字

模式匹配接受一个模式作为输入(跟随 case),以及一个主题值(跟随 match)。该模式(可能包含子模式)与主题值进行匹配。结果是:

  • 匹配成功或失败(也称为模式成功或失败)。

  • 可能将匹配的值绑定到一个名称。其前提条件将在下面进一步讨论。

matchcase 关键字是 软关键字

另请参阅

  • PEP 634 – 结构模式匹配:规范

  • PEP 636 – 结构模式匹配:教程

8.6.1. 概述

以下是 match 语句的逻辑流程概述:

  1. 计算主题表达式 subject_expr 并获得结果主题值。如果主题表达式包含逗号,则使用 标准规则 构造元组。

  2. 尝试将 case_block 中的每个模式与主题值进行匹配。下面将介绍成功或失败的具体规则。匹配尝试还可以绑定模式中的部分或全部独立名称。精确的模式绑定规则因模式类型而异,并在下面指定。在成功的模式匹配期间进行的名称绑定会在执行的块之后继续存在,并且可以在 match 语句之后使用

    注意

    在模式匹配失败期间,某些子模式可能会成功。请不要依赖于为失败的匹配所做的绑定。相反,请不要依赖于在失败的匹配之后变量保持不变。确切的行为取决于实现,并且可能有所不同。这是为了允许不同的实现添加优化而做出的有意决定。

  3. 如果模式成功,则计算相应的保护(如果存在)。在这种情况下,保证发生所有名称绑定。

    • 如果保护评估为 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 后跟一个表达式。

带有 guardcase 块的逻辑流程如下:

  1. 检查 case 块中的模式是否成功。如果模式失败,则不评估 guard,并且检查下一个 case 块。

  2. 如果模式成功,则评估 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 '+' NUMBERsigned_number '-' NUMBER 用于表示复数;它们要求左边是实数,右边是虚数。例如: 3 + 4j

简单来说,LITERAL 仅在 <目标值> == LITERAL 时才会成功。对于单例 NoneTrueFalse,将使用 is 运算符。

8.6.4.4. 捕获模式

捕获模式将目标值绑定到一个名称。 语法

capture_pattern ::=  !'_' NAME

单个下划线 _ 不是捕获模式(这就是 !'_' 所表达的)。而是将其视为通配符模式

在给定的模式中,一个给定的名称只能绑定一次。例如:case x, x: ... 无效,而 case [x] | x: ... 允许。

捕获模式始终成功。绑定遵循 PEP 572 中赋值表达式运算符建立的作用域规则;该名称将成为最近的包含函数作用域中的局部变量,除非有适用的 globalnonlocal 语句。

简单来说,NAME 始终会成功,并且会设置 NAME = <目标值>

8.6.4.5. 通配符模式

通配符模式始终成功(匹配任何内容)并且不绑定任何名称。 语法

wildcard_pattern ::=  '_'

_ 是任何模式中的软关键字,但仅在模式内。即使在 match 目标表达式、guardcase 代码块内,它也是一个标识符,与往常一样。

简单来说,_ 始终会成功。

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])仍然是一个序列模式。

一个序列模式中最多可以有一个星号子模式。星号子模式可以出现在任何位置。如果不存在星号子模式,则该序列模式是固定长度的序列模式;否则,它是可变长度的序列模式。

以下是将序列模式与目标值匹配的逻辑流程

  1. 如果目标值不是[2]序列,则序列模式失败。

  2. 如果目标值是 strbytesbytearray 的实例,则序列模式失败。

  3. 后续步骤取决于序列模式是固定长度还是可变长度。

    如果序列模式是固定长度的

    1. 如果目标序列的长度不等于子模式的数量,则序列模式失败

    2. 序列模式中的子模式从左到右与其在目标序列中的对应项进行匹配。一旦子模式失败,匹配就会停止。如果所有子模式都成功匹配了对应的项,则序列模式成功。

    否则,如果序列模式是可变长度的

    1. 如果目标序列的长度小于非星号子模式的数量,则序列模式失败。

    2. 前导的非星号子模式与固定长度序列的对应项进行匹配。

    3. 如果上一步成功,则星号子模式匹配由剩余的目标项形成的列表,不包括星号子模式之后剩余的与非星号子模式对应的项。

    4. 其余的非星号子模式与它们对应的目标项进行匹配,就像固定长度序列一样。

    注意

    目标序列的长度是通过 len() 获取的(即通过 __len__() 协议)。解释器可以像值模式一样缓存此长度。

简单来说, [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

以下是将映射模式与主题值进行匹配的逻辑流程:

  1. 如果主题值不是映射 [3],则映射模式匹配失败。

  2. 如果映射模式中给出的每个键都存在于主题映射中,并且每个键的模式都与主题映射的相应项匹配,则映射模式匹配成功。

  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

同一个关键字不应在类模式中重复。

以下是将类模式与主题值进行匹配的逻辑流程:

  1. 如果 name_or_attr 不是内置的 type 的实例,则引发 TypeError

  2. 如果主题值不是 name_or_attr 的实例(通过 isinstance() 测试),则类模式匹配失败。

  3. 如果没有模式参数,则模式匹配成功。否则,后续步骤取决于是否存在关键字或位置参数模式。

    对于一些内置类型(如下所述),接受单个位置子模式,该子模式将匹配整个主题;对于这些类型,关键字模式的工作方式与其他类型相同。

    如果只存在关键字模式,则它们按以下方式逐个处理:

    I. 关键字作为主题的属性进行查找。

    • 如果此操作引发了 AttributeError 之外的异常,则该异常会向上冒泡。

    • 如果此操作引发了 AttributeError,则类模式匹配失败。

    • 否则,与关键字模式关联的子模式会与主题的属性值进行匹配。如果此操作失败,则类模式匹配失败;如果此操作成功,则匹配继续进行到下一个关键字。

    II. 如果所有关键字模式都匹配成功,则类模式匹配成功。

    如果存在任何位置模式,则在使用类 name_or_attr 上的 __match_args__ 属性匹配之前,将它们转换为关键字模式。

    I. 调用等效于 getattr(cls, "__match_args__", ()) 的代码。

    • 如果此操作引发了异常,则该异常会向上冒泡。

    • 如果返回的值不是元组,则转换失败并引发 TypeError

    • 如果位置模式的数量多于 len(cls.__match_args__),则会引发 TypeError

    • 否则,位置模式 i 将使用 __match_args__[i] 作为关键字转换为关键字模式。__match_args__[i] 必须是字符串;如果不是,则会引发 TypeError

    • 如果存在重复的关键字,则会引发 TypeError

    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

  • … 以及对应的关键字参数/模式对等等。

另请参阅

  • PEP 634 – 结构化模式匹配:规范

  • PEP 636 – 结构化模式匹配:教程

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 的函数的局部变量。 有关详细信息,请参见 命名与绑定 一节。

另请参阅

PEP 3107 - 函数注解

函数注解的原始规范。

PEP 484 - 类型提示

为注解定义标准含义:类型提示。

PEP 526 - 变量注解的语法

能够为变量声明(包括类变量和实例变量)添加类型提示。

PEP 563 - 注解的延迟求值

支持注解中的前向引用,方法是在运行时以字符串形式保留注解,而不是急切求值。

PEP 318 - 函数和方法的装饰器

引入了函数和方法装饰器。 在 PEP 3129 中引入了类装饰器。

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” 这种表示法进行访问,当以这种方式访问时,实例属性会隐藏同名的类属性。类属性可以用作实例属性的默认值,但使用可变值可能会导致意外的结果。描述器可用于创建具有不同实现细节的实例变量。

另请参阅

PEP 3115 - Python 3000 中的元类

该提案将元类的声明更改为当前语法,以及具有元类的类是如何构造的语义。

PEP 3129 - 类装饰器

该提案添加了类装饰器。函数和方法装饰器是在 PEP 318 中引入的。

8.9. 协程

在版本 3.5 中添加。

8.9.1. 协程函数定义

async_funcdef ::=  [decorators] "async" "def" funcname "(" [parameter_list] ")"
                   ["->" expression] ":" suite

Python 协程的执行可以在多个点暂停和恢复(请参阅协程)。await 表达式、async forasync with 只能在协程函数的主体中使用。

使用 async def 语法定义的函数始终是协程函数,即使它们不包含 awaitasync 关键字。

在协程函数的主体中使用 yield from 表达式是 SyntaxError 错误。

一个协程函数的例子

async def func(param1, param2):
    do_stuff()
    await some_coroutine()

在 3.7 版本中更改:awaitasync 现在是关键字;以前它们只在协程函数的主体中被视为关键字。

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

异步上下文管理器是一个上下文管理器,它能够在它的 enterexit 方法中暂停执行。

以下代码

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.TypeVarTupletyping.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):
    ...

除了 延迟求值 TypeVar 的绑定外,这等同于:

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 之类的大写字母开头的名称实际上在运行时不会绑定。

脚注