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 通过要求嵌套的 else 语句进行缩进来解决“悬挂 else”问题)。

为了清晰起见,以下各节中语法规则的格式将每个子句放在单独的一行。

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_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 语句生成异常的信息,请参阅第 The raise statement 节。

8.4.1. except 子句

except 子句指定一个或多个异常处理程序。当 try 子句中没有发生异常时,不会执行任何异常处理程序。当 try 代码块中发生异常时,将开始搜索异常处理程序。此搜索将依次检查 except 子句,直到找到与异常匹配的子句。如果存在无表达式的 except 子句,则必须将其放在最后;它将匹配任何异常。

对于带有表达式的 except 子句,表达式必须计算为异常类型或异常类型的元组。引发的异常将匹配其表达式计算为异常类的 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*子句用于处理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 语句的 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. 概述

以下是匹配语句逻辑流程的概述

  1. 主体表达式 subject_expr 被评估,并获得一个主体值。如果主体表达式包含逗号,则使用 标准规则 构造元组。

  2. 尝试将 case_block 中的每个模式与主体值匹配。成功或失败的具体规则将在下面描述。匹配尝试还可以绑定模式中的一些或所有独立名称。精确的模式绑定规则因模式类型而异,并在下面指定。**在成功模式匹配期间进行的名称绑定将超出执行的块的范围,并且可以在匹配语句之后使用**。

    注意

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

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

    • 如果保护评估为真或不存在,则执行 case_block 内部的 block

    • 否则,尝试下一个 case_block,如上所述。

    • 如果没有其他 case 块,则匹配语句完成。

注意

用户通常不应该依赖于模式的评估。根据实现,解释器可能会缓存值或使用其他优化来跳过重复评估。

一个示例匹配语句

>>> 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 条件评估为真,则选择 case 块。

    • 如果 guard 条件评估为假,则不会选择 case 块。

    • 如果 guard 在评估期间引发异常,则异常会冒泡。

保护允许具有副作用,因为它们是表达式。保护评估必须从第一个到最后一个 case 块依次进行,一次一个,跳过模式不都成功的 case 块。(即,保护评估必须按顺序发生。)一旦选择了一个 case 块,保护评估就必须停止。

8.6.3. 不可反驳的 Case 块

不可反驳的 case 块是一个匹配所有情况的 case 块。一个 match 语句最多只能有一个不可反驳的 case 块,并且它必须放在最后。

如果一个 case 块没有 guard 并且它的模式是不可反驳的,那么它就被认为是不可反驳的。如果我们能够仅从其语法证明一个模式总是会成功,那么该模式就被认为是不可反驳的。只有以下模式是不可反驳的

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 | "-" NUMBER

规则 strings 和标记 NUMBER标准 Python 语法 中定义。支持三引号字符串。支持原始字符串和字节字符串。不支持 f-字符串

形式 signed_number '+' NUMBERsigned_number '-' NUMBER 用于表示 复数;它们要求左侧为实数,右侧为虚数。例如 3 + 4j

简单来说,LITERAL 只有在 <subject> == LITERAL 时才会成功。对于单例 NoneTrueFalse,使用 is 运算符。

8.6.4.4. 捕获模式

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

capture_pattern ::=  !'_' NAME

单个下划线 _ 不是捕获模式(这就是 !'_' 所表达的)。相反,它被视为一个 wildcard_pattern

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

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

简单来说,NAME 将始终成功,并且它将设置 NAME = <subject>

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 仅当 <subject> == 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>] 仅在以下所有情况发生时匹配

  • 检查<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

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

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

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

  3. 如果在映射模式中检测到重复键,则该模式被视为无效。对于重复的文字值,将引发 SyntaxError;对于具有相同值的命名键,将引发 ValueError

注意

键值对使用映射主题的 get() 方法的双参数形式进行匹配。匹配的键值对必须已存在于映射中,并且不能通过 __missing__()__getitem__() 动态创建。

简单来说,{KEY1: P1, KEY2: P2, ... } 仅在以下所有情况发生时才匹配

  • 检查 <subject> 是否为映射

  • KEY1 in <subject>

  • P1<subject>[KEY1] 匹配

  • … 以及相应的 KEY/模式对的后续匹配。

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   ::=  "*" [parameter] ("," defparameter)* ["," ["**" parameter [","]]]
                               | "**" parameter [","]
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

参数可以具有 注释,形式为“: expression”,位于参数名称之后。任何参数都可以有注释,即使是 *identifier**identifier 形式的参数。函数可以具有“返回”注释,形式为“-> expression”,位于参数列表之后。这些注释可以是任何有效的 Python 表达式。注释的存在不会改变函数的语义。注释值作为字典的值可用,字典的键是函数对象 __annotations__ 属性中参数的名称。如果使用来自 __future__annotations 导入,则注释在运行时将被保留为字符串,这使得延迟评估成为可能。否则,它们将在函数定义执行时被评估。在这种情况下,注释的评估顺序可能与它们在源代码中出现的顺序不同。

还可以创建匿名函数(未绑定到名称的函数),以便在表达式中立即使用。这使用 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 版本中添加。

type_params  ::=  "[" type_param ("," type_param)* "]"
type_param   ::=  typevar | typevartuple | paramspec
typevar      ::=  identifier (":" expression)?
typevartuple ::=  "*" identifier
paramspec    ::=  "**" identifier

函数(包括 协程)、类型别名 可以包含类型参数列表

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 不能有边界或约束。

以下示例表示允许的类型参数声明的完整集合

def overly_generic[
   SimpleTypeVar,
   TypeVarWithBound: int,
   TypeVarWithConstraints: (str, bytes),
   *SimpleTypeVarTuple,
   **SimpleParamSpec,
](
   a: SimpleTypeVar,
   b: TypeVarWithBound,
   c: Callable[SimpleParamSpec, TypeVarWithConstraints],
   *d: 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. 泛型类型别名

The 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 这样的首字母大写的名称在运行时实际上并没有绑定。

脚注