Python中的结构模式匹配
在Python 3.10之前,我们没有任何内置的方法来使用结构模式匹配,在其他编程语言中被称为switch-case
。从 Python 3.10 发布以来,我们不能使用match ... case
语句来模拟switch ... case
语句。
本教程介绍了结构模式匹配和它在 Python 中的重要性。它还使用不同的模式来演示使用match ... case
语句。
结构模式匹配及其重要性的介绍
在2021年的早期,我们不能在小于或等于3.9的已发布的Python版本中使用match
这个关键字。那时,我们习惯于使用字典或嵌套的if/elif/else
语句来模拟switch ... case
。
但是,Python 3.10 引入了一个新的功能,称为结构模式匹配 (match ... case
语句)。它相当于我们在Java、C++和其他许多编程语言中的switch ... case
语句。
这个新特性使我们能够编写简单的、易读的、最不容易出错的流程控制语句。
在Python中使用结构模式匹配
结构模式匹配是作为switch ... case
语句使用的,而且比这更强大。如何做到的呢?让我们在下面探讨一些例子,了解它们在不同情况下的用途。
match ... case
语句的基本用法
示例代码:
colour = "blue"
match colour:
case "green":
print("The specified colour is green")
case "white":
print("Wow, you've picked white")
case "green":
print("Great, you are going with green colour")
case "blue":
print("Blue like sky...")
OUTPUT:
Blue like sky...
这里,我们首先有一个包含blue
的变量colour
。然后,我们使用match
关键字,它将colour
变量的值与各种指定的情况相匹配,其中每个情况以case
关键字开头,后面是我们要比较或检查的模式。
该模式可以是以下的一种:
- 字面模式
- 捕获模式
- 通配符模式
- 恒定值模式
- 序列模式
- 映射模式
- 类模式
- OR模式
- 海象模式
match ... case
语句只运行第一个匹配的case
下的代码。
如果没有匹配的case
,怎么办?用户怎么会知道呢?为此,我们可以有一个默认的case
,如下所示。
示例代码:
colour = "yellow"
match colour:
case "green":
print("The specified colour is green")
case "white":
print("Wow, you've picked white")
case "green":
print("Great, you are going with green colour")
case "blue":
print("Blue like sky...")
case other:
print("No match found!")
OUTPUT:
No match found!
使用match ... case
来检测和解构数据结构
示例代码:
student = {
"name": {"first": "Mehvish", "last": "Ashiq"},
"section": "B"
}
match student:
case {"name": {"first": firstname}}:
print(firstname)
OUTPUT:
Mehvish
在上面的例子中,结构模式匹配在以下两行代码中发挥作用:
match student:
case {"name": {"first": firstname}}:
我们使用match ... case
语句,通过从student
数据结构中提取学生的名字来查找。这里,student
是一个包含学生信息的字典。
case
行指定了我们的模式来匹配student
。考虑到上面的例子,我们寻找一个带有the "name"
键的字典,其值是一个新的字典。
这个嵌套的字典包含一个"first"
key,其值被绑定到firstname
变量。最后,我们使用firstname
变量来打印这个值。
如果你更深入地观察,我们在这里已经学会了映射模式。怎么会呢?映射模式看起来像{"student": s, "emails": [*es]}
,它至少与一组指定的键相匹配的映射。
如果所有的子模式都与它们相应的值相匹配,那么它就把匹配过程中的任何一个子模式与键所对应的值进行绑定。如果我们想允许捕获额外的项目,我们可以在模式的末尾添加**rest
。
在捕获模式和序列模式中使用match ... case
示例代码:
def sum_list_of_numbers(numbers):
match numbers:
case []:
return 0
case [first, *rest]:
return first + sum_list_of_numbers(rest)
sum_list_of_numbers([1,2,3,4])
OUTPUT:
10
在这里,我们使用递归函数来使用捕获模式来捕获与指定模式相匹配的内容,并将其绑定到名称上。
在这个代码例子中,第一个case
,如果它与空列表匹配,则返回0
,作为一个总和。第二个case
使用带有两个捕获模式的序列模式来匹配具有多个项目/元素之一的列表。
这里,列表中的第一个项目被捕获并绑定到first
名称,而第二个捕获模式*rest
,使用解包语法来匹配任何数量的项目/元素。
请注意,rest
绑定到具有所有数字项目/元素的列表,不包括第一个项目。为了得到输出,我们通过传递上面给出的数字列表来调用sum_list_of_numbers()
函数。
使用match ... case
与通配符模式
示例代码:
def sum_list_of_numbers(numbers):
match numbers:
case []:
return 0
case [first, *rest]:
return first + sum_list_of_numbers(rest)
case _:
incorrect_type = numbers.__class__.__name__
raise ValueError(f"Incorrect Values. We Can only Add lists of numbers,not {incorrect_type!r}")
sum_list_of_numbers({'1':'2','3':'4'})
OUTPUT:
ValueError: Incorrect Values. We Can only Add lists of numbers, not 'dict'
在学习match ... case
语句的基本用法时,我们已经学习了使用通配符模式的概念,但没有介绍通配符模式术语。想象一下这样的情景:前两种情况都不匹配,我们需要有一个通配符模式作为我们最后的case
。
例如,如果我们得到任何其他类型的数据结构而不是列表,我们想引发一个错误。在这里,我们可以使用_
作为通配符模式,它将匹配任何没有绑定到名字的东西。我们在这个最后的case
中添加错误处理,以告知用户。
你怎么说?我们的模式可以使用吗?让我们通过传递一个字符串值的列表来调用sum_list_of_numbers()
函数进行测试,如下所示:
sum_list_of_numbers(['1','2','3','4'])
它将产生以下错误:
TypeError: can only concatenate str (not "int") to str
所以,我们可以说,这个模式还是不够万无一失。为什么呢?因为我们向sum_list_of_numbers()
函数传递了列表类型的数据结构,但有字符串类型的值,而不是我们预期的int类型。
请看下面的章节,了解如何解决这个问题。
使用match ... case
与类模式
示例代码:
def sum_list_of_numbers(numbers):
match numbers:
case []:
return 0
case [int(first), *rest]:
return first + sum_list_of_numbers(rest)
case _:
raise ValueError(f"Incorrect values! We can only add lists of numbers")
sum_list_of_numbers(['1','2','3','4'])
OUTPUT:
ValueError: Incorrect values! We can only add lists of numbers
基本情况 (第一个case
) 返回0
;因此,求和只对我们可以用数字加的类型有效。注意,Python 不知道如何添加文本字符串和数字。
因此,我们可以使用类模式来限制我们的模式,使之只匹配整数。类模式类似于映射模式,但是匹配的是属性而不是键。
使用match ... case
与OR模式
示例代码:
def sum_list_of_numbers(numbers):
match numbers:
case []:
return 0
case [int(first) | float(first), *rest]:
return first + sum_list_of_numbers(rest)
case _:
raise ValueError(f"Incorrect values! We can only add lists of numbers")
假设我们想让sum_list_of_numbers()
函数对一个列表中的值起作用,无论是int型还是float型值。我们使用用管号表示的OR模式 (|
)。
如果指定的列表包含int或float类型以外的值,上述代码必须引发ValueError
。让我们考虑以下三种情况进行测试。
测试1:通过一个有int类型值的列表:
sum_list_of_numbers([1,2,3,4]) #output is 10
测试2:通过一个有浮动类型值的列表:
sum_list_of_numbers([1.0,2.0,3.0,4.0]) #output is 10.0
测试3:通过一个有除int和float类型以外的任何其他类型的列表:
sum_list_of_numbers(['1','2','3','4'])
#output is ValueError: Incorrect values! We can only add lists of numbers
你可以看到,由于使用了OR模式,sum_list_of_numbers()
函数对int和float类型的值都有效。
使用match ... case
与文字模式
示例代码:
def say_hello(name):
match name:
case "Mehvish":
print(f"Hi, {name}!")
case _:
print("Howdy, stranger!")
say_hello("Mehvish")
OUTPUT:
Hi, Mehvish!
这个例子使用了匹配字面对象的字面模式,例如,一个明确的数字或字符串,正如我们在学习match ... case
语句的基本用法时已经做的那样。
它是最基本的模式类型,让我们模拟类似于Java、C++和其他编程语言的switch ... case
语句。你可以访问这个页面来了解所有的模式。