Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

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

作者:
Brandt Bucher <brandt at python.org>, Guido van Rossum <guido at python.org>
BDFL 委托

讨论至:
Python-Dev 列表
状态:
最终版
类型:
标准跟踪
创建日期:
2020年9月12日
Python 版本:
3.10
发布历史:
2020年10月22日,2021年2月8日
取代:
622
决议:
Python提交者信息

目录

重要

本 PEP 是一份历史文档。最新的规范文档现在可以在 match 语句 中找到。

×

有关如何提出更改,请参阅 PEP 1

摘要

本 PEP 提供了 match 语句的技术规范。它取代了 PEP 622,该 PEP 现已分为三个部分:

本 PEP 刻意避免了评论;动机和所有设计选择的解释都在 PEP 635 中。建议初次阅读的读者从 PEP 636 开始,它对模式的概念、语法和语义提供了更温和的介绍。

语法和语义

有关完整语法,请参阅 附录 A

概述和术语

模式匹配过程将模式(紧随 case)和主题值(紧随 match)作为输入。描述该过程的短语包括“模式与主题值匹配”和“我们将模式与主题值匹配”。

模式匹配的主要结果是成功或失败。如果成功,我们可以说“模式成功”、“匹配成功”或“模式匹配主题值”。

在许多情况下,模式包含子模式,成功或失败由这些子模式与值(例如,对于 OR 模式)或值的某些部分(例如,对于序列模式)匹配的成功或失败决定。此过程通常从左到右处理子模式,直到确定总体结果。例如,OR 模式在第一个成功的子模式处成功,而序列模式在第一个失败的子模式处失败。

模式匹配的次要结果可能是一个或多个名称绑定。我们可以说“模式将值绑定到名称”。当子模式尝试直到第一次成功时,只有由于成功子模式而产生的绑定才有效;当尝试直到第一次失败时,绑定将被合并。以下解释了适用于这些情况的更多规则。

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
guard: 'if' named_expression

规则 star_named_expressionstar_named_expressionsnamed_expressionblock标准 Python 语法 的一部分。

规则 patterns 在下面指定。

就上下文而言,match_stmtcompound_statement 的一个新替代项。

compound_statement:
    | if_stmt
    ...
    | match_stmt

matchcase 关键字是软关键字,即它们在其他语法上下文中(包括如果行首没有预期的冒号)不是保留字。这意味着它们仅在作为 match 语句或 case 块的一部分时才被识别为关键字,并允许在所有其他上下文中用作变量或参数名。

match 语义

match 语句首先评估主题表达式。如果存在逗号,则使用标准规则构造一个元组。

然后,使用结果主题值来选择第一个其模式成功匹配主题值*并且*其守护条件(如果存在)为“真值”的 case 块。如果没有 case 块符合条件,则 match 语句完成;否则,执行所选 case 块的块。适用于嵌套在复合语句中的块的常规执行规则适用(例如 if 语句)。

在成功模式匹配期间进行的名称绑定在执行块之后仍然有效,并可在 match 语句之后使用。

在失败的模式匹配期间,某些子模式可能会成功。例如,在将模式 (0, x, 1) 与值 [0, 1, 2] 匹配时,如果列表元素从左到右匹配,则子模式 x 可能会成功。实现可以选择为这些部分匹配创建持久绑定或不创建。包含 match 语句的用户代码不应依赖于为失败匹配创建的绑定,也不应假设变量未因失败匹配而更改。行为的这一部分故意未指定,以便不同的实现可以添加优化,并防止引入可能限制此功能可扩展性的语义限制。

精确的模式绑定规则因模式类型而异,并将在下面指定。

守卫

如果 case 块上存在守护条件,一旦 case 块中的一个或多个模式成功,守护条件中的表达式就会被评估。如果这引发异常,异常将冒泡。否则,如果条件为“真值”,则选择 case 块;如果为“假值”,则不选择 case 块。

由于守护条件是表达式,它们允许有副作用。守护条件评估必须从第一个 case 块到最后一个 case 块依次进行,跳过其模式未能全部成功的 case 块。(即,即使确定这些模式是否成功可能乱序发生,守护条件评估也必须按顺序发生。)一旦选择了一个 case 块,守护条件评估必须停止。

不可驳回的case块

如果仅从其语法就可以证明一个模式总是成功,则认为它是不可驳回的。特别是,捕获模式和通配符模式是不可驳回的,左侧不可驳回的 AS 模式、包含至少一个不可驳回模式的 OR 模式以及带括号的不可驳回模式也是如此。

如果一个 case 块没有守护条件且其模式是不可驳回的,则认为该 case 块是不可驳回的。

一个 match 语句最多可以有一个不可驳回的 case 块,并且它必须是最后一个。

模式

模式的顶层语法如下:

patterns: open_sequence_pattern | pattern
pattern: as_pattern | or_pattern
as_pattern: or_pattern 'as' capture_pattern
or_pattern: '|'.closed_pattern+
closed_pattern:
    | literal_pattern
    | capture_pattern
    | wildcard_pattern
    | value_pattern
    | group_pattern
    | sequence_pattern
    | mapping_pattern
    | class_pattern

AS 模式

语法

as_pattern: or_pattern 'as' capture_pattern

(注意:右侧的名称不能是 _。)

AS 模式将 as 关键字左侧的 OR 模式与主题匹配。如果匹配失败,AS 模式失败。否则,AS 模式将主题绑定到 as 关键字右侧的名称并成功。

OR 模式

语法

or_pattern: '|'.closed_pattern+

当两个或多个模式由竖线 (|) 分隔时,这称为 OR 模式。(单个封闭模式就是它本身。)

只有最后一个子模式可以是不可驳回的。

每个子模式必须绑定相同的名称集合。

OR 模式依次将其每个子模式与主题匹配,直到一个成功。然后 OR 模式被视为成功。如果没有子模式成功,OR 模式失败。

字面量模式

语法

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 形式仅允许用于表达复数;它们要求左侧为实数,右侧为虚数。

如果主题值与字面量表示的值相等,则字面量模式成功,使用以下比较规则:

  • 数字和字符串使用 == 运算符进行比较。
  • 单例字面量 NoneTrueFalse 使用 is 运算符进行比较。

捕获模式

语法

capture_pattern: !"_" NAME

单个下划线 (_) 不是捕获模式(这是 !"_" 所表达的)。它被视为 通配符模式

捕获模式总是成功。它使用 PEP 572 中为海象运算符建立的名称绑定作用域规则将主题值绑定到名称。(摘要:名称成为最近的包含函数作用域中的局部变量,除非存在适用的 nonlocalglobal 语句。)

在给定模式中,给定名称只能绑定一次。例如,这禁止 case x, x: ... 但允许 [x] | x: ...

通配符模式

语法

wildcard_pattern: "_"

通配符模式总是成功。它不绑定任何名称。

值模式

语法

value_pattern: attr
attr: name_or_attr '.' NAME
name_or_attr: attr | NAME

模式中的点分名称使用标准 Python 名称解析规则进行查找。但是,当同一值模式在同一 match 语句中多次出现时,解释器可能会缓存找到的第一个值并重用它,而不是重复相同的查找。(为了澄清,此缓存严格与给定 match 语句的给定执行绑定。)

如果找到的值与主题值相等(使用 == 运算符)则模式成功。

分组模式

语法

group_pattern: '(' pattern ')'

(有关 pattern 的语法,请参阅上面的 Patterns。请注意,它不包含逗号 – 带括号的且至少有一个逗号的项系列是序列模式,() 也是。)

带括号的模式没有额外的语法。它允许用户在模式周围添加括号以强调预期的分组。

序列模式

语法

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)

(请注意,单个不带尾随逗号的带括号模式是组模式,而不是序列模式。但是,用 [...] 括起来的单个模式仍然是序列模式。)

使用 [...] 的序列模式、使用 (...) 的序列模式和开放序列模式之间没有语义差异。

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

为了使序列模式成功,主题必须是序列,其中序列的定义是其类属于以下之一:

  • 继承自 collections.abc.Sequence 的类
  • 已注册为 collections.abc.Sequence 的 Python 类
  • 设置了 Py_TPFLAGS_SEQUENCE 位的内置类
  • 继承自上述任何一个的类(包括在父类的 Sequence 注册*之前*定义的类)

以下标准库类的 Py_TPFLAGS_SEQUENCE 位将被设置:

  • array.array
  • collections.deque
  • 列表
  • memoryview
  • range
  • 元组

注意

尽管 strbytesbytearray 通常被视为序列,但它们不包含在上述列表中,也不匹配序列模式。

如果主题序列的长度不等于子模式的数量,则固定长度序列模式失败。

如果主题序列的长度小于非星号子模式的数量,则可变长度序列模式失败。

主题序列的长度使用内置的 len() 函数(即,通过 __len__ 协议)获得。但是,解释器可以以类似于值模式中描述的方式缓存此值。

固定长度序列模式将子模式与主题序列的相应项从左到右匹配。一旦子模式失败,匹配停止(并导致失败)。如果所有子模式都成功匹配其相应的项,则序列模式成功。

可变长度序列模式首先将前导的非星号子模式与主题序列的相应项匹配,如同固定长度序列一样。如果成功,星号子模式将匹配一个由剩余主题项组成的列表,其中从末尾删除的项对应于星号子模式后面的非星号子模式。然后,剩余的非星号子模式将与相应的主题项匹配,如同固定长度序列一样。

映射模式

语法

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

(请注意,此语法不允许 **_。)

一个映射模式最多可以包含一个双星号模式,并且它必须是最后一个。

一个映射模式不能包含重复的键值。(如果所有键模式都是字面量模式,则这被认为是语法错误;否则这是运行时错误,并将引发 ValueError。)

为了使映射模式成功,主题必须是映射,其中映射的定义是其类属于以下之一:

  • 继承自 collections.abc.Mapping 的类
  • 已注册为 collections.abc.Mapping 的 Python 类
  • 设置了 Py_TPFLAGS_MAPPING 位的内置类
  • 继承自上述任何一个的类(包括在父类的 Mapping 注册*之前*定义的类)

标准库类 dictmappingproxy 将设置其 Py_TPFLAGS_MAPPING 位。

如果映射模式中给出的每个键都存在于主题映射中,并且每个键的模式都与主题映射的相应项匹配,则映射模式成功。键总是使用 == 运算符进行比较。如果存在 '**' NAME 形式,则该名称绑定到包含主题映射中剩余键值对的 dict

如果在映射模式中检测到重复的键,则该模式被视为无效,并引发 ValueError

键值对使用主题 get() 方法的两个参数形式进行匹配。因此,匹配的键值对必须已经存在于映射中,而不是通过 __missing____getitem__ 动态创建。例如,collections.defaultdict 实例将只匹配在 match 语句进入时已经存在键的模式。

类模式

语法

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 不是内置 type 的实例,则引发 TypeError

如果主题不是 name_or_attr 的实例,则类模式失败。这使用 isinstance() 进行测试。

如果没有参数,如果 isinstance() 检查成功,则模式成功。否则:

  • 如果只存在关键字模式,它们将按以下方式逐一处理:
    • 关键字被查找为主题上的属性。
      • 如果这引发了 AttributeError 以外的异常,则异常冒泡。
      • 如果这引发了 AttributeError,则类模式失败。
      • 否则,与关键字关联的子模式与属性值匹配。如果匹配失败,则类模式失败。如果匹配成功,则匹配继续到下一个关键字。
    • 如果所有关键字模式都成功,则整个类模式成功。
  • 如果存在任何位置模式,它们将被转换为关键字模式(见下文),并被视为附加的关键字模式,位于语法关键字模式(如果有)之前。

位置模式使用 name_or_attr 指定的类上的 __match_args__ 属性转换为关键字模式,如下所示:

  • 对于一些内置类型(下面指定),接受单个位置子模式,它将匹配整个主题。(关键字模式在这里像其他类型一样工作。)
  • 调用 getattr(cls, "__match_args__", ()) 的等效操作。
  • 如果这引发异常,则异常冒泡。
  • 如果返回的值不是元组,则转换失败并引发 TypeError
  • 如果位置模式的数量多于 __match_args__ 的长度(通过 len() 获取),则引发 TypeError
  • 否则,位置模式 i 将使用 __match_args__[i] 作为关键字转换为关键字模式,前提是后者是字符串;如果不是,则引发 TypeError
  • 对于重复的关键字,将引发 TypeError

一旦位置模式转换为关键字模式,匹配过程将如同只有关键字模式一样进行。

如上所述,对于以下内置类型,位置子模式的处理方式不同:boolbytearraybytesdictfloatfrozensetintlistsetstrtuple

这种行为大致等同于以下内容:

class C:
    __match_args__ = ("__match_self_prop__",)
    @property
    def __match_self_prop__(self):
        return self

副作用和未定义行为

模式匹配过程明确产生的唯一副作用是名称绑定。然而,该过程依赖于主题及其某些组件上的属性访问、实例检查、len()、相等性和项访问。它还评估值模式和类模式的类名。虽然这些通常不会产生任何副作用,但理论上它们可能会。本提案有意省略了关于调用哪些方法或调用多少次的任何规范。因此,此行为是未定义的,用户代码不应依赖它。

另一个未定义的行为是,在同一 case 块中,捕获模式后面跟着另一个失败模式时,变量的绑定。这可能会根据实现策略提前或延后发生,唯一的限制是捕获变量必须在使用它们的守护条件被评估之前设置。如果守护条件包含 and 子句,则操作数的评估甚至可能与模式匹配交错进行,只要保持从左到右的评估顺序。

标准库

为了方便使用模式匹配,标准库将进行以下几项更改:

  • 具名元组和数据类将自动生成 __match_args__
  • 对于数据类,生成的 __match_args__ 中属性的顺序将与生成的 __init__() 方法中相应参数的顺序相同。这包括属性从超类继承的情况。带有 init=False 的字段将从 __match_args__ 中排除。

此外,将系统性地努力审查现有标准库类,并在有益之处添加 __match_args__

附录 A – 完整语法

这是 match_stmt 的完整语法。这是 compound_stmt 的一个附加替代项。请记住,matchcase 是软关键字,即它们在其他语法上下文中(包括如果行首没有预期冒号)不是保留字。按照惯例,硬关键字使用单引号,而软关键字使用双引号。

除了标准 EBNF 之外使用的其他符号:

  • SEP.RULE+RULE (SEP RULE)* 的简写
  • !RULE 是否定先行断言
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
guard: 'if' named_expression

patterns: open_sequence_pattern | pattern
pattern: as_pattern | or_pattern
as_pattern: or_pattern 'as' capture_pattern
or_pattern: '|'.closed_pattern+
closed_pattern:
    | literal_pattern
    | capture_pattern
    | wildcard_pattern
    | value_pattern
    | group_pattern
    | sequence_pattern
    | mapping_pattern
    | class_pattern

literal_pattern:
    | signed_number !('+' | '-')
    | signed_number '+' NUMBER
    | signed_number '-' NUMBER
    | strings
    | 'None'
    | 'True'
    | 'False'
signed_number: NUMBER | '-' NUMBER

capture_pattern: !"_" NAME !('.' | '(' | '=')

wildcard_pattern: "_"

value_pattern: attr !('.' | '(' | '=')
attr: name_or_attr '.' NAME
name_or_attr: attr | NAME

group_pattern: '(' pattern ')'

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)

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

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

来源:https://github.com/python/peps/blob/main/peps/pep-0634.rst

最后修改:2023-12-11 05:40:56 GMT