Python match语句

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)…… 剩余对应的关键字参数/模式对也以此类推。

广告合作
QQ群号:707632017

温馨提示:

1、本网站发布的内容(图片、视频和文字)以原创、转载和分享网络内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。邮箱:2942802716#qq.com。(#改为@)

2、本站原创内容未经允许不得转裁,转载请注明出处“站长百科”和原文地址。

目录