增加了采用模式加上相应动作的 match 语句 和 case 语句 的形式的结构化模式匹配。 模式由序列、映射、基本数据类型以及类实例构成。 模式匹配使得程序能够从复杂的数据类型中提取信息、根据数据结构实现分支,并基于不同的数据形式应用特定的动作。
语法与操作
模式匹配的通用语法如下:
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 代码块不执行任何操作。
声明性方式
读者可能是通过 C, Java 或 JavaScript (以及其他许多语言) 中的 switch 语句将一个目标 (数据对象) 与一个字面值 (模式) 进行匹配的简单例子了解到模式匹配的概念的。 switch 语句常常被用来将一个对象/表达式与包含在 case 语句中的字面值进行比较。
更强大的模式匹配例子可以在 Scala 和 Elixir 等语言中找到。 这种结构化模式匹配方式是“声明性”的并且会显式地为所要匹配的数据指定条件(模式)。
虽然使用嵌套的“if”语句的“命令性”系列指令可以被用来完成类似结构化模式匹配的效果,但它没有“声明性”方式那样清晰。 相反地,“声明性”方式指定了一个匹配所要满足的条件,并且通过其显式的模式使之更为易读。 虽然结构化模式匹配可以采取将一个变量与一个 case 语句中的字面值进行比较的最简单形式来使用,但它对于 Python 的真正价值在于其针对目标类型和形状的处理操作。
简单模式:匹配一个字面值
让我们把这个例子看作是模式匹配的最简单形式:一个值,即主词,被匹配到几个字面值,即模式。在下面的例子中,status
是匹配语句的主词。模式是每个 case 语句,字面值代表请求状态代码。匹配后,将执行与该 case 相关的动作:
注:case后面跟变量,与case _是一样的效果
def http_error(status):match status:case 400:return "Bad request"case 404:return "Not found"case 418:return "I'm a teapot"case _:return "Something's wrong with the internet"
如果传给上述函数的 status
为 418,则会返回 "I'm a teapot"。 如果传给上述函数的 status
为 500,则带有 _
的 case 语句将作为通配符匹配,并会返回 "Something's wrong with the internet"。 请注意最后一个代码块:变量名 _
将作为 通配符 并确保目标将总是被匹配。 _
的使用是可选的。
你可以使用 |
(“ or ”)在一个模式中组合几个字面值:
case 401 | 403 | 404:return "Not allowed"
无通配符的行为
如果我们修改上面的例子,去掉最后一个 case 块,这个例子就变成:
def http_error(status):match status:case 400:return "Bad request"case 404:return "Not found"case 418:return "I'm a teapot"
如果不在 case 语句中使用 _
,可能会出现不存在匹配的情况。如果不存在匹配,则行为是一个 no-op。例如,如果传入了值为 500 的 status
,就会发生 no-op。
带有字面值和变量的模式
模式可以看起来像解包形式,而且模式可以用来绑定变量。在这个例子中,一个数据点可以被解包为它的 x 坐标和 y 坐标:
# point is an (x, y) tuple match point:case (0, 0):print("Origin")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("Not a point")
第一个模式有两个字面值 (0, 0)
,可以看作是上面所示字面值模式的扩展。接下来的两个模式结合了一个字面值和一个变量,而变量 绑定 了一个来自主词的值(point
)。 第四种模式捕获了两个值,这使得它在概念上类似于解包赋值 (x, y) = point
。
as关键字使用别名
set=(3,7) match set:case(1,3):print("1 or 3")case ((3,7) as x):print(f"{x}")case _:print("other")
匹配模型也可以使用数据类型来匹配
list01 = [1, 2, 3, 4] list02 = (10, 2) match list02:#模式匹配可以在case后面使用if条件语句,也可以使用as关键字来给变量赋值case tuple([a,b]) if a>3:print("list01 is a tuple")case list([x,y]):#数据类型+元素数量print("list01 is a list")case set():print("list01 is a set")case _: # 这里的 _ 代表其他任何类型print("list01 is not a tuple or a list")
匹配模式可解包
# 定义一个列表
list01 = [1, 2, 3, 4, 5, 6]# 使用模式匹配语法进行匹配
match list01:
# 如果列表中至少有两个元素,将前两个元素分别赋值给变量x和y,剩余的元素赋值给变量z
case [x, y, *z]:
print(f"x={x}, y={y}, rest={z}")
# 如果列表中的元素不满足前面的模式,则执行这个默认情况
case _:
print("Invalid pattern")
case [x, y, *z]
:这个模式匹配语句检查列表是否至少有两个元素,如果是,它会将前两个元素分别赋值给变量x
和y
,剩余的元素赋值给变量z
。case _
:这个是默认情况,匹配所有其它情况。
在这个例子中,由于列表list01
有至少两个元素,因此第一个模式被匹配。x
被赋值为1,y
被赋值为2,剩余的元素3、4、5和6被赋值给变量z
。所以,最后输出的结果是:x=1, y=2, rest=[3, 4, 5, 6]
。