Python中的match语句用于从字符串的起始位置匹配一个模式,如果匹配成功,返回一个匹配对象(Match Object),否则返回None。本教程将介绍 match 语句的基本语法、逻辑流程、模式等等。
一、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 后),该模式(可能包含子模式)将与目标值进行匹配。输出是:
- 匹配成功或失败(也被称为模式成功或失败);
- 可能将匹配的值绑定到一个名字上,这方面的先决条件将在下面进一步讨论。
关键字 match 和 case 是 soft keywords 。
二、逻辑流程
匹配语句逻辑流程的概述如下:
1、对目标表达式 subject_expr 求值后将结果作为匹配用的目标值,如果目标表达式包含逗号,则使用 the standard rules 构建一个元组。
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 是约束项。
三、约束项
guard ::= "if" named_expression
guard (它是 case 的一部分) 必须成立才能让 case 语句块中的代码被执行。 它所采用的形式为: if 之后跟一个表达式。
拥有 guard 的 case 块的逻辑流程如下:
1、检查 case 块中的模式是否匹配成功。如果该模式匹配失败,则不对 guard 进行求值,检查下一个 case 块。
2、如果该模式匹配成功,对 guard 求值。
- 如果 guard 求值为真,则选用该 case 块;
- 如果 guard 求值为假,则不选用该 case 块;
- 如果在对 guard 求值过程中引发了异常,则异常将被抛出。
允许约束项产生副作用,因为他们是表达式。约束项求值必须从第一个 case 块到最后一个 case 块依次逐个进行,模式匹配失败的 case 块将被跳过。(也就是说,约束项求值必须按顺序进行。)一旦选用了一个 case 块,约束项求值必须由此终止。
四、必定匹配的case 块
必定匹配的 case 块是能匹配所有情况的 case 块。一个匹配语句最多可以有一个必定匹配的 case 块,而且必须是最后一个。
如果一个 case 块没有约束项,并且其模式是必定匹配的,那么它就被认为是必定匹配的。 如果我们可以仅从语法上证明一个模式总是能匹配成功,那么这个模式就被认为是必定匹配的。 只有以下模式是必定匹配的:
- 左侧模式是必定匹配的 AS 模式;
- 包含至少一个必定匹配模式的 或模式;
- 捕获模式;
- 通配符模式;
- 括号内的必定匹配模式。
五、模式
注意:本节使用了超出标准 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
下面的描述将包括一个“简而言之”以描述模式的作用,便于说明问题。请注意,这些描述纯粹是为了说明问题,可能不 反映底层的实现,它们并没有涵盖所有有效的形式。
1、或模式
或模式是由竖杠 | 分隔的两个或更多的模式。语法:
or_pattern ::= "|".closed_pattern+
只有最后的子模式可以是 必定匹配的,且每个子模式必须绑定相同的名字集以避免歧义。
或模式将目标值依次与其每个子模式尝试匹配,直到有一个匹配成功,然后该或模式被视作匹配成功。 否则,如果没有任何子模式匹配成功,则或模式匹配失败。简而言之,P1 | P2 | … 会首先尝试匹配 P1 ,如果失败将接着尝试匹配 P2 ,如果出现成功的匹配则立即结束且模式匹配成功,否则模式匹配失败。
2、AS模式
AS 模式将关键字 as 左侧的或模式与目标值进行匹配。语法:
as_pattern ::= or_pattern "as" capture_pattern
如果 OR 模式匹配失败,则 AS 模式也会失败。 在其他情况下,AS 模块会将目标与 as 关键字右边的名称绑定并匹配成功。 capture_pattern 不可为 _。简而言之, P as NAME 将与 P 匹配,成功后将设置 NAME = <subject> 。
3、字面值模式
字面值模式对应 Python 中的大多数 字面值。 语法为:
literal_pattern ::= signed_number | signed_number "+" NUMBER | signed_number "-" NUMBER | strings | "None" | "True" | "False" | signed_number: NUMBER | "-" NUMBER
规则 strings 和标记 NUMBER 是在 standard Python grammar 中定义的,支持三引号的字符串,不支持原始字符串和字节字符串,也不支持格式字符串字面值 。
signed_number ‘+’ NUMBER 和 signed_number ‘-‘ NUMBER 形式是用于表示 复数;它们要求左边是一个实数而右边是一个虚数, 例如 3 + 4j。简而言之, LITERAL 只会在 <subject> == LITERAL 时匹配成功。对于单例 None 、 True 和 False ,会使用 is 运算符。
4、捕获模式
捕获模式将目标值与一个名称绑定。语法:
capture_pattern ::= !'_' NAME
单独的一个下划线 _ 不是捕获模式( !’_’ 表达的就是这个含义), 它会被当作 wildcard_pattern 。在给定的模式中,一个名字只能被绑定一次。例如 case x, x: … 时无效的,但 case [x] | x: … 是被允许的。
捕获模式总是能匹配成功。绑定遵循 PEP 572 中赋值表达式运算符设立的作用域规则;名字在最接近的包含函数作用域内成为一个局部变量,除非有适用的 global 或 nonlocal 语句。简而言之, NAME 总是会匹配成功且将设置 NAME = <subject> 。
5、通配符模式
通配符模式总是会匹配成功(匹配任何内容)并且不绑定任何名称。语法:
wildcard_pattern ::= '_'
在且仅在任何模式中 _ 是一个 软关键字。 通常情况下它是一个标识符,即使是在 match 的目标表达式、guard 和 case 代码块中也是如此。简而言之,_ 总是会匹配成功。
6、值模式
值模式代表 Python 中具有名称的值。语法:
value_pattern ::= attr attr ::= name_or_attr "." NAME name_or_attr ::= attr | NAME
模式中带点的名称会使用标准的 Python 名称解析规则 来查找。 如果找到的值与目标值比较结果相等则模式匹配成功(使用 == 相等运算符)。简而言之, NAME1.NAME2 仅在 <subject> == NAME1.NAME2 时匹配成功。
如果相同的值在同一个匹配语句中出现多次,解释器可能会缓存找到的第一个值并重新使用它,而不是重复查找。 这种缓存与特定匹配语句的执行严格挂钩。
7、组模式
组模式允许用户在模式周围添加括号,以强调预期的分组,除此之外,它没有额外的语法。语法:
group_pattern ::= "(" pattern ")"
简单来说 (P) 具有与 P 相同的效果。
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]) 则仍是一个序列模式。一个序列模式中最多可以有一个星号子模式。星号子模式可以出现在任何位置。如果没有星号子模式,该序列模式是固定长度的序列模式;否则,其是一个可变长度的序列模式。
下面是将一个序列模式与一个目标值相匹配的逻辑流程:
- 如果目标值不是一个序列 2 ,该序列模式匹配失败;
- 如果目标值是 str 、 bytes 或 bytearray 的实例,则该序列模式匹配失败;
- 随后的步骤取决于序列模式是固定长度还是可变长度的。
如果序列模式是固定长度的:
- 如果目标序列的长度与子模式的数量不相等,则该序列模式匹配失败;
- 序列模式中的子模式与目标序列中的相应项目从左到右进行匹配。 一旦一个子模式匹配失败,就停止匹配。 如果所有的子模式都成功地与它们的对应项相匹配,那么该序列模式就匹配成功了。
否则,如果序列模式是变长的:
- 如果目标序列的长度小于非星号子模式的数量,则该序列模式匹配失败;
- 与固定长度的序列一样,靠前的非星形子模式与其相应的项目进行匹配;
- 如果上一步成功,星号子模式与剩余的目标项形成的列表相匹配,不包括星号子模式之后的非星号子模式所对应的剩余项;
- 剩余的非星号子模式将与相应的目标项匹配,就像固定长度的序列一样。
注意:目标序列的长度可通过 len() (即通过 __len__() 协议) 获得。 解释器可能会以类似于 值模式 的方式缓存这个长度信息。简而言之, [P1, P2, P3, … , P<N>] 仅在满足以下情况时匹配成功:
- 检查 <subject> 是一个序列;
- len(subject) == <N>;
- 将 P1 与 <subject>[0] 进行匹配(请注意此匹配可以绑定名称);
- 将 P2 与 <subject>[1] 进行匹配(请注意此匹配可以绑定名称);
- …… 剩余对应的模式/元素也以此类推。
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 。
以下是映射模式与目标值匹配的逻辑流程:
- 如果目标值不是一个映射 3,则映射模式匹配失败;
- 若映射模式中给出的每个键都存在于目标映射中,且每个键的模式都与目标映射的相应项匹配成功,则该映射模式匹配成功;
- 如果在映射模式中检测到重复的键,该模式将被视作无效。对于重复的字面值,会引发 SyntaxError ;对于相同值的命名键,会引发 ValueError 。
注意:键值对使用映射目标的 get() 方法的双参数形式进行匹配。 匹配的键值对必须已经存在于映射中,而不是通过 __missing__() 或 __getitem__() 即时创建。简而言之, {KEY1: P1, KEY2: P2, … } 仅在满足以下情况时匹配成功:
- 检查 <subject> 是映射;
- KEY1 in <subject>;
- P1 与 <subject>[KEY1] 相匹配;
- …… 剩余对应的键/模式对也以此类推。
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
同一个关键词不应该在类模式中重复出现。
以下是类模式与目标值匹配的逻辑流程:
- 如果 name_or_attr 不是内置 type 的实例,引发 TypeError;
- 如果目标值不是 name_or_attr 的实例(通过 isinstance() 测试),该类模式匹配失败;
- 如果没有模式参数存在,则该模式匹配成功。 否则,后面的步骤取决于是否有关键字或位置参数模式存在。
对于一些内置的类型(将在后文详述),接受一个位置子模式,它将与整个目标值相匹配;对于这些类型,关键字模式也像其他类型一样工作。如果只存在关键词模式,它们将被逐一处理,如下所示:
(1)该关键词被视作主体的一个属性进行查找;
- 如果这引发了除 AttributeError 以外的异常,该异常会被抛出;
- 如果这引发了 AttributeError ,该类模式匹配失败;
- 否则,与关键词模式相关的子模式将与目标的属性值进行匹配。 如果失败,则类模式匹配失败;如果成功,则继续对下一个关键词进行匹配。
(2)如果所有的关键词模式匹配成功,该类模式匹配成功。
- 如果存在位置模式,在匹配前会用类 name_or_attr 的 __match_args__ 属性将其转换为关键词模式。
(3)进行与 getattr(cls, “__match_args__”, ()) 等价的调用。
- 如果这引发一个异常,该异常将被抛出;
- 如果返回值不是一个元组,则转换失败且引发 TypeError ;
- 若位置模式的数量超出 len(cls.__match_args__) ,将引发 TypeError ;
- 否则,位置模式 i 会使用 __match_args__[i] 转换为关键词。 __match_args__[i] 必须是一个字符串;如果不是则引发 TypeError ;
- 如果有重复的关键词,引发 TypeError 。
(4)若所有的位置模式都被转换为关键词模式,匹配的过程就像只有关键词模式一样。
对于以下内置类型,位置子模式的处理是不同的:
- bool
- bytearray
- bytes
- dict
- float
- frozenset
- int
- list
- set
- str
- tuple
这些类接受一个位置参数,其模式是针对整个对象而不是某个属性进行匹配。 例如,int(0|1) 匹配值 0,但不匹配值 0.0。简而言之, CLS(P1, attr=P2) 仅在满足以下情况时匹配成功:
(1)isinstance(<subject>, CLS)。
(2)用 CLS.__match_args__ 将 P1 转换为关键词模式。
(3)对于每个关键词参数 attr=P2 :
- hasattr(<subject>, “attr”);
- 将 P2 与 <subject>.attr 进行匹配。
(4)…… 剩余对应的关键字参数/模式对也以此类推。