Python3.10重磅新特性:用模式匹配优化代码
2610 views, 2021/10/22 updated Go to Comments
前几天 Python 3.10 版本发布了,里面就包含了模式匹配这个重磅新特性。
合理运用模式匹配,毫无疑问可以显著优化你的代码。
让我们好好聊聊它。
值模式
下面就是一个简单的模式匹配语句:
flag = 1 match flag: case 0: print('0') case 1: print('1') case _: print('None') # 输出: 1
- 关键字
match
后的是被匹配的值。 - 解释器会将此值逐个与关键字
case
后的值做对比,若相等则进入此case
语句,其他case
语句不再执行。 - 因此上面的代码进入了
case 1
,打印了字符串 ”1“ 。 case _
表示如果以上所有值都没能匹配,那就进入这条语句块。
好家伙,这和 if..elif..else
有区别吗?
把它们放到一起对比:
match flag: case 0: print('0') case 1: print('1') case 2: print('2') case _: print('None') # -------------------- if flag == 0: print('0') elif flag == 1: print('1') elif flag == 2: print('2') else: print('None')
不知道你的感受如何,笔者是觉得模式匹配语句容易理解得多,最起码你少写了很多个 flag ==
吧(重复书写是好代码的敌人)。
如果有很多 if..elif..else
语句写在一坨,你需要小心去观察里面这些 elif/else
是否从属于同一个 if
;而 match
因为其缩进原因,逻辑块的从属关系会非常清晰。
此外, if 语句容易出错,比如漏写 else
,或者将 elif
写成 if
之类的沙雕操作;而模式匹配出这种低级错误的可能性小得多。
在很多语言中,模式匹配的执行效率更高,且容易维护。
如果不同的值需要进入相同的语句块,你可以用竖线 |
将其联合:
def http_error(status): match status: case 400: print("Bad request") case 404: print("Not found") case 418: print("I'm a teapot") case 401 | 403: print("Not allowed") case _: print("Something's wrong with the internet") http_error(418) # I'm a teapot http_error(403) # Not allowed http_error(999) # Something's wrong with the internet
很简单吧,下面看点更高级的。
序列模式
如果你学过 C、Java 或 JavaScript 中的 switch 语句,那么则对 Python 的模式匹配并不陌生。不同的是, switch 语句常被用来将对象与 case 中的字面值进行比较;但 Python 的模式匹配可以处理更强大的结构化数据。
比如,模式匹配可以处理元组:
def foo(point): # point 是两个元素的序列 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 _: print("Not a point") foo((0, 0)) # Origin foo((0, 99)) # Y=99 foo((99, 99)) # X=99, Y=99 foo((1, 2, 3)) # Not a point foo(0) # Not a point
元组中的变量 x, y
代表此处可以为任意值,并且此变量可以传递给 case
中的代码块。
注意:序列模式中,使用列表或者元组是没有差别的。
准确地说,要使得序列模式匹配成功,只要对象是
collections.abc.Sequence
的子类就可以(即为一个序列),哪怕这个对象不是内置的列表或元组。
看下面这个例子,它可以成功匹配 case (0, 0)
:
foo([0, 0]) # Origin
除了可以用变量 x, y
等变量来代表任意值外,case _
同样可以用在结构化数据中:
match event: case ('warning', 500): print("A warning has been received.") case ('error', _): print(f"An error occurred.")
类模式
类(class)也可用于模式匹配。
看下面这个匹配坐标的例子:
class Point: x: int y: int def location(event): match event: 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") p = Point() (p.x, p.y) = (0, 9) location(p) # Y=9 and the point is on the y-axis.
位置参数
上面这种 Point(x=0, y=0)
的写法稍微有点繁琐。如果你想用位置参数初始化对象,可以用数据类 dataclass
,像这样:
from dataclasses import dataclass @dataclass class Point: x: int y: int def location(point): match point: case Point(0, 0): print("Origin is the point's location.") case Point(0, y): print(f"Y={y} and the point is on the y-axis.") case Point(x, 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") p = Point(0, 9) location(p) # Y=9 and the point is on the y-axis.
注意仔细对比代码的变化。
嵌套
综合上面的例子,模式匹配能任意地嵌套,比如下面这种:
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.")
上面的代码,对空序列、单元素序列和多元素序列都做了妥善的处理。
这就是所谓的结构化数据模式匹配,是不是有点感觉了。
守护项
你可以在模式匹配中设置匹配条件。
比如 if
语句:
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 Click: x: int y: int @dataclass class KeyPress: name: str def action(event): match event: case Click(0, 0): print("Origin is the point's location.") case KeyPress('space'): print("Space key pressed.") case [Click(x, y), KeyPress('shift')]: print(f"Zoomming position at {x}, {y}") case _: print("Nothing happend.")
上面这个例子中 Click
表示用户点击了鼠标动作,KeyPress
表示按键动作。模式匹配可以将这两个不同的对象同时进行处理,这个看似很常用的操作,在某些语言中是办不到的。
枚举模式
模式匹配还可以处理枚举:
from enum import Enum class Color(Enum): RED = 0 GREEN = 1 BLUE = 2 def get_color(color): 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 :(")
映射模式
也可以处理字典:
persons = {'David': 19, 'Dusai': 21, 'Jack': 23} match persons: case {'Dusai': 21, 'Jack': 23}: print('Dusai and Jack.') case {'David': _}: print('David.') # 输出: Dusai and Jack.
对字典的处理要注意,模式匹配会捕获自己需要的键值,而额外的键值会被忽略。
总结
以上就是模式匹配的大部分基础使用场景了。
模式匹配比 if
语句更易阅读,通常执行效率也更高。
Python 中的模式匹配可以用于结构化数据,使得其应用场景(比某些编程语言中单纯比对字面量)更加广阔。
赶紧升级 Python 3.10,用它改造你的旧代码吧!