Python 3.10 实现的 match 功能是其他开发语言中传统的 switch 的超集,它不仅仅是 switch,可以实现更为复杂的功能。模式匹配大大增加了控制流的清晰度和表达能力。
背景
虽然使用嵌套的“if”语句的“命令性”系列指令可以被用来完成类似结构化模式匹配的效果,但它没有“声明性”方式那样清晰。相反地,“声明性”方式指定了一个匹配所要满足的条件,并且通过其显式的模式使之更为易读。更强大的模式匹配例子可以在 Scala 和 Elixir 等语言中找到。 这种结构化模式匹配方式是“声明性”的并且会显式地为所要匹配的数据指定条件(模式)。
一直以来,Python 没有其他语言的 switch 方法来实现多条件分支,要求支持的呼声很高,Python 3.10.0 支持了它,而且是超级版的,实现的思路与它们大有不同。match 与 case 配合,由 case 从上到下将目标与语句中的每个模式进行比较,直到确认匹配,执行相应的模式下的语句。如果未确认完全匹配,则最终用通配符 _(如提供)将用作匹配情况。如所有的都不匹配且没有通配符,则相当于空操作。
模式由序列、映射、基本数据类型以及类实例构成。 模式匹配使得程序能够从复杂的数据类型中提取信息、根据数据结构实现分支,并基于不同的数据形式应用特定的动作。
语法
采用模式加上相应动作的 match 语句 和 case 语句 的形式的结构化模式匹配。 其中 match 与 case 在特点句法下为 Python 的软关键字(Soft keywords):
match subject:
case <pattern_1>:
<action_1>
case <pattern_2>:
<action_2>
case <pattern_3>:
<action_3>
case _:
<action_wildcard>
match 语句接受一个表达式并将其值与以一个或多个 case 语句块形式给出的一系列模式进行比较。 具体来说,模式匹配的操作如下:
给定具有特定类型和结构的数据 (subject)
针对 subject 在 match 语句中求值
从上到下对 subject 与 case 语句中的每个模式进行比较:
直到确认匹配到一个模式,执行与被确认匹配的模式相关联的动作
如果没有确认到一个完全的匹配:
如果提供了使用通配符 _ 的最后一个 case 语句,则它将被用作已匹配模式
不存在使用通配符的 case 语句,则整个 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
注意,对 subject 的匹配不仅为具体值,还可以包括它的数据结构,可以匹配到全部结构和部分结构内容,并将这些匹配到的结构内容引用到关联的处理动作中作为变量。具体的理解可以看看下方的用法示例。另外:
匹配的不能是一个表达式,一定要是一个字面值或者结构
类似于解包赋值功能
元组和列表模式具有完全相同的含义,而且实际上能匹配任意序列
目标必须为一个序列
例外:模式不能匹配迭代器
序列模式不能匹配字符串(为了避免一个常见的错误)
支持通配符: [x, y, *rest] 和 (x, y, *rest) 的作用类似于解包赋值中的通配符
在 * 之后的名称也可以为_,因此 (x, y, *) 可以匹配包含两个条目的序列而不必绑定其余的条目
映射模式,如 {"bandwidth": b, "latency": l} 会从一个字典中捕获 "bandwidth" 和 "latency" 值,与序列模式不同,额外的键会被忽略。 也支持通配符 **rest。(但 ** 是冗余的,因而不被允许)
用法
结构化模式匹配可以采取将一个变量与一个 case 语句中的字面值进行比较的最简单形式来使用,但 Python 的模式匹配真正针对的目标类型和形状的处理操作。接下来就用一些例子来说明:
匹配一个字面值
这是最简单的应用,一个值,即主词,被匹配到几个字面值,即模式。在下面的例子中,status 是匹配语句的主词。模式是每个 case 语句,字面值代表请求状态代码。匹配后,将执行与该 case 相关的动作:
grade = 3
match grade:
case 1:
print('一年级')
case 2:
print('二年级')
case 3:
print('三年级')
case _:
print('未知年级')
三年级
变量名 _ 作为 通配符 并确保目标将总是被匹配,是可选的,如果没有又之前 case 未得到匹配,则会执行一个空操作(no-op)。还可以用 | (表示或者)在一个模式中组合几个字面值:
grade = 5
match grade:
case 1:
print('一年级')
case 2:
print('二年级')
case 3:
print('三年级')
case 4 | 5 | 6:
print('高年级')
case _:
print('未知年级')
高年级
下边是一利用一个类状态实现的开关功能:
class switch:
on = 1
off = 0
status = 0
match status:
case switch.on :
print('Switch is on')
case switch.off :
print('Switch is off')
守卫 Guard
可以在 case 中编写 if 条件语句,实现与 if 语句类似的功能:
score = 81
match score:
case 100:
print('满分!')
case score if score >= 80:
print(f'高分啊~ {score}')
case score if score >= 60:
print('及格万岁!')
case _:
print('不知道说什么。')
高分啊~ 81
在 case 后面可以加入一个 if 判断作为守卫,如匹成功但守卫为假,则继续尝试下一个 case 案例块,值捕获发生在评估守卫之前。再来一个例子:
def fun(score):
match score:
case 100:
print('满分!')
case score if score >= 80:
print(f'高分啊~ {score}')
case score if score >= 60:
print('及格万岁!')
case score if score in [57, 58, 59]:
print('这成绩也是没谁了 ··!')
case _:
print('不知道说什么。')
fun(59)
这成绩也是没谁了 ··!
带有字面值和变量的模式
模式可以看起来像解包形式,而且模式可以用来绑定变量。在这个例子中,一个数据点可以被解包为它的 x 坐标和 y 坐标:
point is an (x, y) tuple
match point:
case (0, 0):
print("坐标原点")
case (0, y):
print(f"Y={y}")
case (x, 0):
print(f"X={x}")
case (x, y):
print(f"X={x}, Y={y}")
case _:
raise ValueError("未法的坐标数据")
第一个模式有两个字面值 (0, 0) ,可以看作是上面所示字面值模式的扩展。接下来的两个模式结合了一个字面值和一个变量,而变量 绑定 了一个来自主词的值(point)。 第四种模式捕获了两个值,这使得它在概念上类似于解包赋值 (x, y) = point 。
这种情况也可以增加守卫:
point = (60, 0)
match point:
case (0, 0):
print("坐标原点")
case (0, y):
print(f"Y={y}")
case (x, 0) if x > 50:
print(f"X={x},点在 x 轴的远处")
case (x, 0):
print(f"X={x}")
case (x, y):
print(f"X={x}, Y={y}")
case _:
raise ValueError("未法的坐标数据")
X=60,点在 x 轴的远处
命名的常量
模式可以使用命名的常量,且必须使用 . 以防止被解释为捕获变量:
from enum import Enum
class Color(Enum):
RED = 0
GREEN = 1
BLUE = 2
match color:
case Color.RED:
print("I see red!")
case Color.GREEN:
print("Grass is green")
case Color.BLUE:
print("I'm feeling the blues