该特性已经有 final 版本 since Python 3.10,出自 PEP 636,因此本文就该版本完整介绍 match 语句的各种花里胡哨的用法。
match 语句,或者说是 match-case 语句更为适合,和其他语言的 switch-case 语句类似,用作多条件的分支选择。在 Python 中,case 关键词的后面叫做模式(pattern)。
匹配字面值
这是最基本的用法,和:
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"
这是一个根据状态码返回响应消息的函数,不同值对应不同的结果,特别地,_
是通配符,表示一定会匹配成功,必须在最后一个 case
分支,充当类似其他语言 default 分支的功能,但是位置没有 default 自由。
大多数字面值是按相等性比较的,但是单例对象
True
,False
和None
则是按标识号比较的。
使用 |
在一个模式中还可以组合多个字面值:
case 401 | 403 | 404:
return "Not allowed"
|
不止用于值的组合,后续介绍的更为复杂模式都可以通过该符号表“或”关系,详见后面示例。
匹配常量
模式可以使用命名常量。 这些命名常量必须为带点号的名称以防止它们被解读为捕获变量,说白了就是枚举。
这个没什么说的,直接上实例,后续结合其他模式再给出复杂示例。
from enum import Enum
class Color(Enum):
RED = 'red'
GREEN = 'green'
BLUE = 'blue'
color = Color(input("Enter your choice of 'red', 'blue' or 'green': "))
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 :(")
匹配序列
常见的序列如列表、集合、元组等,这里以列表为例,给出一个比较综合的示例:
def match_sequence(seq):
match seq:
case []:
print("序列为空")
case [a]:
print(f"序列只有一个元素:{a}")
case [x, y]:
print(f"序列有两个元素,[{x}, {y}]")
case [b, 1]:
print(f"序列有两个元素,第一个元素为 {b}")
case [_, _, c]:
print(f"序列有三个元素,第三个元素为 {c}")
case _:
print(f"序列未知")
match_sequence([]) # 序列为空
match_sequence([2]) # 序列只有一个元素:2
match_sequence([4, 1]) # 序列有两个元素,[4, 1]
match_sequence([1, 2, 3]) # 序列有三个元素,第三个元素为 3
match_sequence([1, 2, 3, 4, 5]) # 序列未知
- 匹配序列时,会按照元素顺序逐一比较
- 模式中可以使用变量进行解构赋值
- 会按照从上到下的顺序匹配模式,匹配成功就执行子句返回结果
- 序列中可以使用
_
职位占位符,表示那里有元素,但不关注值,注意与模式_
含义区分- 不能匹配迭代器和字符串
扩展解构
序列模式支持扩展解构操作:[x, y, *rest]
和 (x, y, *rest)
的作用类似于解包赋值。 在 *
之后的名称也可以为 _
,因此,(x, y, *_)
可以匹配包含至少两个条目的序列,而不必绑定其余的条目。
def match_sequence2(seq):
match seq:
case [1, *p]:
print(p)
case [3, a, *_]:
print(f"a={a}")
case [_, _, *q]:
print(q)
match_sequence2([1, 2, 3, 4]) # [2, 3, 4]
match_sequence2([3, 4, 5, 6]) # a=4
match_sequence2([2, 3, 4, 5]) # [4, 5]
匹配字典
有了前面的基础,匹配字典值相对容易理解:
def match_dict(d):
match d:
case {"name": name, "age": age}:
print(f"name={name},age={age}")
case {"key": _, "value": value}:
print(f"value={value}")
case {"first": _, **rest}:
print(rest)
case _:
pass
d1 = {"name": "ice", "age": 18}
d2 = {"key": "k", "value": "v"}
d3 = {"first": "one", "second": "two", "third": "three"}
match_dict(d1) # name=ice,age=18
match_dict() # value=v
match_dict(d3) # {'second': 'two', 'third': 'three'}
**rest
等解构操作也支持,但**_
是冗余的,不允许使用。
匹配对象
通过类对象可以结构化你的数据,通过使用类名字后面跟一个类似构造函数的参数列表,这种模式可以将类的属性捕捉到变量中:
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}")
case Point(x=x, y=0):
print(f"X={x}")
case Point(x=m, y=n):
print(f"X={m}, Y={n}")
case Point():
print("这个点不在轴上")
case _:
raise ValueError("未法的坐标数据")
p1 = Point()
p2 = Point()
p2.x = 0
p2.y = 4
p3 = Point()
p3.x = 5
p3.y = 6
location(p1) # 这个点不在轴上
location(p2) # Y=4
location(p3) # X=5, Y=6
- 这里特地将属性定义到类中而不是
__init__
方法中,就是为了区分这种模式和构造函数的区别- 不写参数说明只关注是不是
Point
对象,其属性值无所谓
下面再看个有初始化参数的例子:
class Direction:
def __init__(self, horizontal=None, vertical=None):
self.horizontal = horizontal
self.vertical = vertical
def direction(loc):
match loc:
case Direction(horizontal='east', vertical='north'):
print('You towards northeast')
case Direction(horizontal='east', vertical='south'):
print('You towards southeast')
case Direction(horizontal='west', vertical='north'):
print('You towards northwest')
case Direction(horizontal='west', vertical='south'):
print('You towards southwest')
case Direction(horizontal=None):
print(f'You towards {loc.vertical}')
case Direction(vertical=None):
print(f'You towards {loc.horizontal}')
case _:
print('Invalid Direction')
d1 = Direction('east', 'south')
d2 = Direction(vertical='north')
d3 = Direction('centre', 'centre')
# 应用
direction(d1) # You towards southeast
direction(d2) # You towards north
direction(d3) # Invalid Direction
匹配位置属性
你可以在某些为其属性提供了排序的内置类(例如 dataclass
)中使用位置参数。
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
你也可以通过在你的类中设置 __match_args__
特殊属性来为模式中的属性定义一个专门的位置。
class Point:
__match_args__ = ("x", "y")
x: int
y: int
以下模式是等价的:
Point(1, var)
Point(1, y=var)
Point(x=1, y=var)
Point(y=var, x=1)
都是将 y
属性绑定到 var
变量。
匹配内建类
相当于在解构赋值时加了一层校验,只有符合类型要求的才会匹配成功。
def match_type(arg):
match arg:
case [int(), int(a), str()]:
print(a)
case list(l):
print(l)
case {"one": str(b)}:
print(b)
match_type([1, 2, "3"]) # 2
match_type([1, 2, 3]) # [1, 2, 3]
match_type({"one": "1"}) # 1
嵌套模式
模式可以任意地嵌套。 例如,如果我们的数据是由点组成的短列表(类似 [Point(x1, y1), Point(x2, y2)]
形式),则它可以这样被匹配:
match points:
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("列表中还有其他内容")
或者:
def match_multiple(arg):
match arg:
case ["age", *l]:
print(l)
case ["language", "C++" | "Java" | "Python", *t]:
print(t)
match_multiple(["age", 18, 29, 30]) # [18, 29, 30]
match_multiple(["language", "Java", 2, 3, 4, 5, 6]) # [2, 3, 4, 5, 6]
match_multiple(["language", "Python", 7, 8, 9, 10, "J"]) # [2, 3, 4, 5, 6]
模式捕获
模式捕获是为了在模式匹配成功后,获得该模式或者其子模式的值使用,一般用法是 模式 as 变量名。
match arg:
case "早" | "中" | "晚" as time:
print(time)
case "一" | "二" | "三" as number:
print(number)
case [(a, 2) as t, 3]:
print(t)
条件匹配
为模式添加成为守护项的 if
子句。如果守护项的值为假,则 match
继续匹配下一个 case
语句块。注意,值的捕获发生在守护项被求值之前:
match point:
case Point(x, y) if x == y:
print(f"Y=X at {x}")
case Point(x, y):
print(f"Not on the diagonal")