8. 复合语句 — Python 文档

来自菜鸟教程
Python/docs/3.10/reference/compound stmts
跳转至:导航、​搜索

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。 另请注意,可选的延续子句始终以不能启动语句的关键字开头,因此没有歧义(“悬空 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" expression_list ":" suite
              ["else" ":" suite]

表达式列表被评估一次; 它应该产生一个可迭代的对象。 为 expression_list 的结果创建迭代器。 然后,按照迭代器返回的顺序,对迭代器提供的每个项目执行一次套件。 使用标准分配规则(参见 分配语句 )依次将每个项目分配给目标列表,然后执行套件。 当项目用完时(即当序列为空或迭代器引发 StopIteration 异常时),执行 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() 返回一个整数迭代器,适合模拟 Pascal 的 for i := a to b do 的效果; 例如,list(range(3)) 返回列表 [0, 1, 2]

笔记

当循环修改序列时有一个微妙之处(这只能发生在可变序列,例如 列表)。 内部计数器用于跟踪下一个使用哪个项目,并且在每次迭代时递增。 当这个计数器达到序列的长度时,循环终止。 这意味着如果套件从序列中删除当前(或前一个)项目,则将跳过下一个项目(因为它获取已处理的当前项目的索引)。 同样,如果套件在当前项目之前的序列中插入一个项目,则当前项目将在下一次循环中再次被处理。 这可能会导致令人讨厌的错误,可以通过使用整个序列的一部分制作临时副本来避免这些错误,例如,

for x in a[:]:
    if x < 0: a.remove(x)

8.4. 这try陈述

try 语句为一组语句指定异常处理程序和/或清理代码:

try_stmt  ::=  try1_stmt | try2_stmt
try1_stmt ::=  "try" ":" suite
               ("except" [expression ["as" identifier]] ":" suite)+
               ["else" ":" suite]
               ["finally" ":" suite]
try2_stmt ::=  "try" ":" suite
               "finally" ":" suite

except 子句指定一个或多个异常处理程序。 当 try 子句中没有异常发生时,不执行异常处理程序。 当 try 套件中发生异常时,将开始搜索异常处理程序。 此搜索依次检查 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.exc_info() 访问。 sys.exc_info() 返回一个由异常类、异常实例和回溯对象组成的三元组(参见 标准类型层次结构 部分),标识程序中的点发生异常。 离开异常处理程序时,通过 sys.exc_info() 访问的异常的详细信息将恢复为以前的值:

>>> print(sys.exc_info())
(None, None, None)
>>> try:
...     raise TypeError
... except:
...     print(sys.exc_info())
...     try:
...          raise ValueError
...     except:
...         print(sys.exc_info())
...     print(sys.exc_info())
...
(<class 'TypeError'>, TypeError(), <traceback object at 0x10efad080>)
(<class 'ValueError'>, ValueError(), <traceback object at 0x10efad040>)
(<class 'TypeError'>, TypeError(), <traceback object at 0x10efad080>)
>>> print(sys.exc_info())
(None, None, None)

如果控制流离开 try 套件,没有引发异常,并且没有 returncontinuebreak 语句被执行。 else 子句中的异常不会由前面的 except 子句处理。

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

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

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

returnbreakcontinue 语句在 tryfinally 语句中,finally 子句也执行了“出路”。

函数的返回值由最后执行的 return 语句决定。 由于 finally 子句始终执行,因此在 finally 子句中执行的 return 语句将始终是最后执行的:

>>> def foo():
...     try:
...         return 'try'
...     finally:
...         return 'finally'
...
>>> foo()
'finally'

关于异常的更多信息可以在 Exceptions 部分找到,关于使用 raise 语句生成异常的信息可以在 raise 语句 部分找到。

3.8 版更改: 在 Python 3.8 之前,由于实现问题,finally 子句中的 continue 语句是非法的。


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 语句的执行过程如下:

  1. 对上下文表达式(with_item 中给出的表达式)求值以获得上下文管理器。

  2. 上下文管理器的 __enter__() 被加载以备后用。

  3. 上下文管理器的 __exit__() 被加载以备后用。

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

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

    笔记

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

  6. 套房被执行。

  7. 上下文管理器的 __exit__() 方法被调用。 如果异常导致套件退出,则其类型、值和回溯将作为参数传递给 __exit__()。 否则,提供三个 None 参数。

    如果套件因异常退出,并且 __exit__() 方法的返回值为 false,则重新引发异常。 如果返回值为真,则异常被抑制,并继续执行 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 中的每个模式都试图与主题值匹配。 成功或失败的具体规则如下所述。 匹配尝试还可以绑定模式中的部分或全部独立名称。 精确的模式绑定规则因模式类型而异,如下所述。 在成功模式匹配期间进行的名称绑定比执行的块更有效,并且可以在匹配语句之后使用

    笔记

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

  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 后跟一个表达式。

case 块与 guard 的逻辑流程如下:

  1. 检查 case 块中的模式是否成功。 如果模式失败,则不评估 guard 并检查下一个 case 块。
  2. 如果模式成功,则评估 guard
    • 如果 guard 条件评估为真,则选择 case 块。
    • 如果 guard 条件评估为假,则不选择 case 块。
    • 如果 guard 在评估期间引发异常,则异常会冒泡。

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


8.6.3. 无可辩驳的案例块

一个无可辩驳的 case 块是一个 match-all 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_pattern ::=  "|".closed_pattern+

只有最终的子模式可能是 irrefutable,并且每个子模式必须绑定相同的名称集以避免歧义。

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 中的大多数 literals。 句法:

literal_pattern ::=  signed_number
                     | signed_number "+" NUMBER
                     | signed_number "-" NUMBER
                     | strings
                     | "None"
                     | "True"
                     | "False"
                     | signed_number: NUMBER | "-" NUMBER

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

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

简单来说,只有<subject> == LITERALLITERAL才会成功。 对于单例 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时才会成功

笔记

如果在同一个 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)

如果括号或方括号用于序列模式,则没有区别(即 (...)[...])。

笔记

括在括号中的单个模式,没有尾随逗号(例如 (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/pattern 对。


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. 如果不存在模式参数,则模式成功。 否则,后续步骤取决于是否存在关键字或位置参数模式。

    对于许多内置类型(在下面指定),接受将匹配整个主题的单个位置子模式; 对于这些类型,关键字模式也适用于其他类型。

    如果只存在关键字模式,则按如下方式一一处理:

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

      • 如果这引发了除 AttributeError 之外的异常,则异常会冒泡。

      • 如果这引发 AttributeError,则类模式失败。

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

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

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

    1. 相当于 getattr(cls, "__match_args__", ()) 被调用。

      • 如果这引发异常,则异常会冒泡。

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

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

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

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

    2. 一旦所有位置模式都转换为关键字模式,匹配就好像只有关键字模式一样进行。

    对于以下内置类型,位置子模式的处理是不同的:

    这些类接受单个位置参数,并且那里的模式与整个对象而不是属性匹配。 例如,int(0|1) 匹配值 0,但不匹配值 0.0False

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

函数定义是一个可执行语句。 它的执行将当前本地命名空间中的函数名称绑定到一个函数对象(函数的可执行代码的包装器)。 该函数对象包含对当前全局命名空间的引用,作为调用该函数时要使用的全局命名空间。

函数定义不执行函数体; this 仅在调用函数时执行。 4

一个函数定义可以被一个或多个 decorator 表达式包裹。 在定义函数时,在包含函数定义的范围内评估装饰器表达式。 结果必须是可调用的,以函数对象作为唯一参数调用。 返回值绑定到函数名而不是函数对象。 多个装饰器以嵌套方式应用。 例如,下面的代码

@f1(arg)
@f2
def func(): pass

大致相当于

def func(): pass
func = f1(arg)(f2(func))

除了原来的函数暂时没有绑定到名称func

3.9 版更改: 函数可以用任何有效的 assignment_expression 修饰。 以前,语法的限制要大得多。 有关详细信息,请参阅 PEP 614


当一个或多个 参数 具有 参数 = 表达式 的形式时,该函数被称为具有“默认参数值”。 对于具有默认值的参数,可以从调用中省略相应的 参数 ,在这种情况下,参数的默认值将被替换。 如果参数具有默认值,则直到“*”之前的所有后续参数也必须具有默认值——这是语法未表达的句法限制。

执行函数定义时从左到右计算默认参数值。每次通话。 这对于理解默认参数值何时是可变对象(例如列表或字典)尤其重要:如果函数修改了对象(例如 通过将项目附加到列表),实际上修改了默认参数值。 这通常不是预期的。 解决这个问题的一种方法是使用 None 作为默认值,并在函数体中明确测试它,例如:

def whats_on_the_telly(penguin=None):
    if penguin is None:
        penguin = []
    penguin.append("property of the zoo")
    return penguin

函数调用语义在 Calls 部分有更详细的描述。 函数调用总是为参数列表中提到的所有参数赋值,无论是位置参数、关键字参数还是默认值。 如果存在“*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 - 注释的延迟评估
通过在运行时以字符串形式保留注释而不是预先求值来支持注释中的前向引用。


8.8. 类定义

类定义定义了一个类对象(参见 标准类型层次结构 部分):

classdef    ::=  [decorators] "class" classname [inheritance] ":" suite
inheritance ::=  "(" [argument_list] ")"
classname   ::=  identifier

类定义是一个可执行语句。 继承列表通常给出一个基类列表(更高级的用法参见 Metaclasses),所以列表中的每一项都应该评估为一个允许子类化的类对象。 没有继承列表的类默认从基类 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


程序员注:类定义中定义的变量为类属性; 它们由实例共享。 实例属性可以在self.name = value的方法中设置。 类和实例属性都可以通过符号“self.name”访问,并且实例属性在以这种方式访问时隐藏了具有相同名称的类属性。 类属性可以用作实例属性的默认值,但在那里使用可变值可能会导致意外结果。 Descriptors 可用于创建具有不同实现细节的实例变量。

也可以看看

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 协程的执行可以在许多点暂停和恢复(参见 coroutine)。 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 - 具有异步和等待语法的协程
使协程成为 Python 中适当的独立概念的提议,并添加了支持语法。


脚注

1

除非有 finally 子句碰巧引发另一个异常,否则异常将传播到调用堆栈。 新的异常会导致旧的异常丢失。

2

在模式匹配中,序列被定义为以下之一:


以下标准库类是序列:

笔记

strbytesbytearray 类型的主题值与序列模式不匹配。

3

在模式匹配中,映射被定义为以下之一:


标准库类 dicttypes.MappingProxyType 是映射。

4

作为函数体中第一条语句出现的字符串文字被转换为函数的 __doc__ 属性,因此是函数的 docstring

5

作为类主体中第一条语句出现的字符串文字被转换为命名空间的 __doc__ 项,因此转换为类的 docstring