python:初识v3.10结构化模式匹配

python3.10增加了采用模式加上相应动作的 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 代码块不执行任何操作。

简单模式:匹配一个字面值

让我们把这个例子看作是模式匹配的最简单形式:一个值,即主词,被匹配到几个字面值,即模式。在下面的例子中,status 是匹配语句的主词。模式是每个 case 语句,字面值代表请求状态代码。匹配后,将执行与该 case 相关的动作:

def http_error(status):
match status:
case 400:
return "请求错误!"
case 404:
return "页面不存在!"
case 418:
return "yyds!"
case _:
return "网络有点问题!"


if __name__ == '__main__':
print(http_error(500))

运行结果

网络有点问题!

Process finished with exit code 0

如果传给上述函数的 status 为 418,则会返回 "yyds!"。 如果传给上述函数的 status 为 500,则带有_的 case 语句将作为通配符匹配,并会返回 "网络有点问题!"。 请注意最后一个代码块:变量名_将作为 通配符 并确保目标将总是被匹配。_的使用是可选的。

•你可以使用 | (“ or ”)在一个模式中组合几个字面值:

def http_error(status):
match status:
case 400 | 404 | 403:
return "请求错误!"
case _:
return "网络有点问题!"


 

if name == 'main': print(http_error(500))

运行结果
```python
网络有点问题!

Process finished with exit code 0

•无通配符的行为

如果我们修改上面的例子,去掉最后一个case块,这个例子就变成:

def http_error(status):
match status:
case 400 | 404 | 403:
return "请求错误!"


if __name__ == '__main__':
print(http_error(200))

运行结果

None

Process finished with exit code 0

如果不在case语句中使用 _,可能会出现不存在匹配的情况。如果不存在匹配,则行为是一个无操作行为。例如,如果传入了值为200的 status ,就会直接返回函数的默认值None

带有字面值和变量的模式

模式可以看起来像解包形式,而且模式可以用来绑定变量。 在这个例子中,一个数据点可以被解包为它的x坐标和y坐标:

# point 是一个 (x, y) 元组
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

•一个简单的例子:

point = (60, 60)
match point:
case (0, 0):
print("坐标原点")
case (0, y):
print(f"Y={y}")
case _:
raise ValueError("Not a point")

运行结果

Traceback (most recent call last):
File "E:\git_dfwsgroup\9first\xzProjectTeamApi\lms3_9first_com\apiInterface\web\classify.py", line 16, in <module>
raise ValueError("Not a point")
ValueError: Not a point

Process finished with exit code 1

模式和类

如果你使用类来结构化你的数据,你可以使用类的名字,后面跟一个类似构造函数的参数列表,作为一种模式。这种模式可以将类的属性捕捉到变量中:

class Point:
x: int
y: int

def location(point):
match point:
case Point(x=0, y=0):
print("Origin is the point's location.")
case Point(x=0, y=y):
print(f"Y={y} and the point is on the y-axis.")
case Point(x=x, y=0):
print(f"X={x} and the point is on the x-axis.")
case Point():
print("The point is located somewhere else on the plane.")
case _:
print("Not a point")

•一个简单的例子:

class Point:
x: int
y: int


def location(point):
match point:
case Point(x=0, y=0):
print("原点是点的位置。")
case Point(x=0, y=y):
print(f"Y={y} 这个点在y轴上。")
case Point(x=x, y=0):
print(f"X={x} 这个点在x轴上。")
case Point():
print("这个点位于平面上的其他地方。")
case _:
print("不是一个点")


p = Point()
p.x = 0
p.y = 0
location((p.x, p.y))

运行结果

不是一个点

Process finished with exit code 0

•上述代码中就是把类中的属性值进行判断,当然,如果使用构造方法就会减少对象调用属性的操作:

class Point:
def __init__(self, x, y):
self.x: int = x
self.y: int = y


def location(point):
match point:
case Point(x=0, y=0):
print("原点是点的位置。")
case Point(x=0, y=y):
print(f"Y={y} 这个点在y轴上。")
case Point(x=x, y=0):
print(f"X={x} 这个点在x轴上。")
case Point():
print("这个点位于平面上的其他地方。")
case _:
print("不是一个点")


location(Point(0, 1))

运行结果

Y=1 这个点在y轴上。

Process finished with exit code 0

•您也可以使用官方提供的装饰器操作:

from dataclasses import dataclass


@dataclass
class Point:
x: int
y: int


def location(point):
match point:
case Point(x=0, y=0):
print("原点是点的位置。")
case Point(x=0, y=y):
print(f"Y={y} 这个点在y轴上。")
case Point(x=x, y=0):
print(f"X={x} 这个点在x轴上。")
case Point():
print("这个点位于平面上的其他地方。")
case _:
print("不是一个点")


location(Point(0, 1))

运行结果

Y=1 这个点在y轴上。

Process finished with exit code 0

这个装饰器的作用,就是把没有构造方法(__init__)的类,添加了一个构造方法。

嵌套模式

模式可以任意地嵌套。 例如,如果我们的数据是由点组成的短列表,则它可以这样被匹配:

match points:
case []:
print("No points in the list.")
case [Point(0, 0)]:
print("The origin is the only point in the list.")
case [Point(x, y)]:
print(f"A single point {x}, {y} is in the list.")
case [Point(0, y1), Point(0, y2)]:
print(f"Two points on the Y axis at {y1}, {y2} are in the list.")
case _:
print("Something else is found in the list.")

•一个简单的例子:

from dataclasses import dataclass


@dataclass
class Point:
x: int
y: int


def location(point):
match point:
case []:
print("列表上没有点。")
case [Point(0, 0)]:
print("原点是列表中唯一的点。")
case [Point(x, y)]:
print(f"单点{x}, {y}在列表中。")
case [Point(0, y1), Point(0, y2)]:
print(f"Y轴上{y1}、{y2}处的两点在列表中。")
case _:
print("在列表中找到了其他东西。")


location([Point(0, 2), Point(0, 1)])

运行结果

Y轴上2、1处的两点在列表中。

Process finished with exit code 0

复杂模式和通配符

到目前为止,这些例子仅在最后一个case语句中使用了 _。 但通配符可以被用在更复杂的模式中,例如 ('error', code, _)。 举例来说:

match test_variable:
case ('warning', code, 40):
print("A warning has been received.")
case ('error', code, _):
print(f"An error {code} occurred.")

•一个简单的例子:

def location(point):
match point:
case ('warning', code, 500):
print(f"收到了一个警告 {code}。")
case ('error', code, _):
print(f"发生错误 {code}。")


location(("error", 900, "_"))

运行结果

发生错误 900。

Process finished with exit code 0

守护项

我们可以向一个模式添加if子句,称为“守护项”。 如果守护项为假值,则match将继续尝试下一个 case 语句块。 请注意值的捕获发生在守护项被求值之前:

match point:
case Point(x, y) if x == y:
print(f"The point is located on the diagonal Y=X at {x}.")
case Point(x, y):
print(f"Point is not on the diagonal.")

•一个简单的例子:

from dataclasses import dataclass


@dataclass
class Point:
x: int
y: int


def location(point):
match point:
case Point(x, y) if x == y:
print(f"点位于对角线上的Y=X {x}处。")
case Point(x, y):
print("点不在对角线上。")


location(Point(1, 1))

运行结果

点位于对角线上的Y=X 1处。

Process finished with exit code 0


以上总结或许能帮助到你,或许帮助不到你,但还是希望能帮助到你,如有疑问、歧义,直接私信留言会及时修正发布;非常期待你的点赞和分享哟,谢谢!

未完,待续…

一直都在努力,希望您也是!

微信搜索公众号:就用python

文章作者:李 锋|编辑排版:梁莉莉

 

posted @ 2021-12-07 19:54  一名小测试  阅读(239)  评论(0编辑  收藏  举报