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提供了匹配语句的技术规范。它替换了PEP 622,后者现已分为三个部分
此PEP有意避免评论;动机和所有设计选择说明都在PEP 635中。建议首次阅读者从PEP 636开始,它对模式的概念、语法和语义提供了更温和的介绍。
语法和语义
请参阅附录A以获取完整语法。
概述和术语
模式匹配过程以模式(在case
之后)和主题值(在match
之后)作为输入。描述此过程的短语包括“模式与(或针对)主题值匹配”和“我们将模式与(或针对)主题值匹配”。
模式匹配的主要结果是成功或失败。如果成功,我们可以说“模式成功”、“匹配成功”或“模式匹配主题值”。
在许多情况下,模式包含子模式,并且成功或失败由将这些子模式与值(例如,对于OR模式)或值的一部分(例如,对于序列模式)匹配的成功或失败决定。此过程通常从左到右处理子模式,直到确定总体结果。例如,OR模式在第一个成功的子模式处成功,而序列模式在第一个失败的子模式处失败。
模式匹配的次要结果可能是一个或多个名称绑定。我们可以说“模式将值绑定到名称”。当尝试子模式直到第一个成功时,只有成功子模式导致的绑定有效;当尝试直到第一个失败时,绑定将被合并。下面解释了适用于这些情况的几个其他规则。
匹配语句
语法
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_expression
、star_named_expressions
、named_expression
和block
是标准Python语法的一部分。
规则patterns
在下面指定。
作为上下文,match_stmt
是compound_statement
的新替代方案
compound_statement:
| if_stmt
...
| match_stmt
match
和case
关键字是软关键字,即它们在其他语法上下文中不是保留字(包括如果预期没有冒号的行首)。这意味着它们仅在匹配语句或case块的一部分时被识别为关键字,并且允许在所有其他上下文中用作变量或参数名称。
匹配语义
匹配语句首先计算主题表达式。如果存在逗号,则使用标准规则构造元组。
然后使用生成的主题值选择其模式成功匹配它并且其守卫条件(如果存在)为“真值”的第一个case块。如果没有case块符合条件,则匹配语句完成;否则,将执行所选case块的块。嵌套在复合语句内的块的执行通常规则适用(例如if
语句)。
在成功模式匹配期间进行的名称绑定将超出执行的块,并且可以在匹配语句之后使用。
在模式匹配失败期间,一些子模式可能会成功。例如,当使用值[0, 1, 2]
匹配模式(0, x, 1)
时,如果列表元素从左到右匹配,则子模式x
可能会成功。实现可以选择是否为这些部分匹配创建持久绑定。包含匹配语句的用户代码不应依赖于为失败的匹配创建的绑定,但也不应假设变量在失败的匹配后保持不变。此行为的一部分有意未指定,以便不同的实现可以添加优化,并防止引入可能限制此功能可扩展性的语义限制。
精确的模式绑定规则因模式类型而异,并在下面指定。
守卫
如果case块上存在守卫,则一旦case块中的模式或模式成功,则计算守卫中的表达式。如果这引发异常,则异常冒泡。否则,如果条件为“真值”,则选择case块;如果条件为“假值”,则不选择case块。
由于守卫是表达式,因此允许它们具有副作用。守卫评估必须从第一个到最后一个case块依次进行,一次一个,跳过其模式不都成功的case块。(即,即使确定这些模式是否成功可能发生在无序的情况下,守卫评估也必须按顺序发生。)一旦选择了一个case块,守卫评估必须停止。
不可反驳的case块
如果我们可以仅从其语法证明模式将始终成功,则该模式被认为是不可反驳的。特别是,捕获模式和通配符模式是不可反驳的,AS模式的左侧也是不可反驳的,包含至少一个不可反驳模式的OR模式以及带括号的不可反驳模式也是如此。
如果case块没有守卫并且其模式是不可反驳的,则该case块被认为是不可反驳的。
匹配语句最多可以有一个不可反驳的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 '+' NUMBER
和signed_number '-' NUMBER
仅允许表达复数;它们需要左侧为实数,右侧为虚数。
如果主题值使用以下比较规则与字面量表达的值相等,则字面量模式成功
- 使用
==
运算符比较数字和字符串。 - 使用
is
运算符比较单例字面量None
、True
和False
。
捕获模式
语法
capture_pattern: !"_" NAME
单个下划线 (_
) 不是捕获模式(这就是!"_"
表达的)。它被视为通配符模式。
捕获模式总是成功。它使用PEP 572中为海豹运算符建立的名称绑定作用域规则将主题值绑定到名称。(摘要:除非存在适用的nonlocal
或global
语句,否则该名称将成为最接近的包含函数作用域中的局部变量。)
在给定的模式中,给定的名称只能绑定一次。例如,这禁止case x, x: ...
,但允许case [x] | x: ...
。
通配符模式
语法
wildcard_pattern: "_"
通配符模式总是成功。它不绑定任何名称。
值模式
语法
value_pattern: attr
attr: name_or_attr '.' NAME
name_or_attr: attr | NAME
模式中的点分名称使用标准Python名称解析规则查找。但是,当相同的价值模式在同一匹配语句中出现多次时,解释器可能会缓存找到的第一个值并重用它,而不是重复相同的查找。(澄清一下,此缓存严格绑定到给定匹配语句的给定执行。)
如果找到的值与目标值相等(使用==
运算符),则模式匹配成功。
组模式
语法
group_pattern: '(' 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)
(请注意,没有尾随逗号的单个括号模式是组模式,而不是序列模式。但是,用[...]
括起来的单个模式仍然是序列模式。)
使用[...]
的序列模式、使用(...)
的序列模式和开放序列模式之间没有语义差异。
序列模式最多可以包含一个星号子模式。星号子模式可以出现在任何位置。如果没有星号子模式,则序列模式是固定长度序列模式;否则,它是可变长度序列模式。
为了使序列模式匹配成功,目标必须是序列,其中序列的定义是其类属于以下之一
- 继承自
collections.abc.Sequence
的类 - 已注册为
collections.abc.Sequence
的Python类 - 其
Py_TPFLAGS_SEQUENCE
位已设置的内置类 - 继承自上述任何类的类(包括在父类的
Sequence
注册之前定义的类)
以下标准库类将设置其Py_TPFLAGS_SEQUENCE
位
array.array
collections.deque
list
memoryview
range
tuple
注意
尽管str
、bytes
和bytearray
通常被认为是序列,但它们不包含在上述列表中,并且与序列模式不匹配。
如果目标序列的长度不等于子模式的数量,则固定长度序列模式匹配失败。
如果目标序列的长度小于非星号子模式的数量,则可变长度序列模式匹配失败。
目标序列的长度是使用内置的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
注册之前定义的类)
标准库类dict
和mappingproxy
将设置其Py_TPFLAGS_MAPPING
位。
如果映射模式中给出的每个键都存在于目标映射中,并且每个键的模式都匹配目标映射的对应项,则映射模式匹配成功。键始终使用==
运算符进行比较。如果存在'**' NAME
形式,则该名称将绑定到一个dict
,其中包含来自目标映射的其余键值对。
如果在映射模式中检测到重复键,则该模式被认为无效,并引发ValueError
。
键值对使用目标的get()
方法的双参数形式进行匹配。因此,匹配的键值对必须已存在于映射中,并且不能由__missing__
或__getitem__
动态创建。例如,collections.defaultdict
实例仅与在匹配语句进入时已存在的键的模式匹配。
类模式
语法
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
。
一旦位置模式转换为关键字模式,匹配将继续进行,就像只有关键字模式一样。
如上所述,对于以下内置类型,位置子模式的处理方式有所不同:bool
、bytearray
、bytes
、dict
、float
、frozenset
、int
、list
、set
、str
和tuple
。
此行为大致等效于以下内容
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
的另一个可选替代方案。请记住,match
和 case
是软关键字,即在其他语法上下文中它们不是保留字(包括在行首,如果预期位置没有冒号)。按照惯例,硬关键字使用单引号,而软关键字使用双引号。
超出标准 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
版权
本文档置于公有领域或 CC0-1.0-Universal 许可证下,以两者中较宽松者为准。
来源:https://github.com/python/peps/blob/main/peps/pep-0634.rst
上次修改时间:2023-12-11 05:40:56 GMT