说明
《Python 教程》 持续更新中,提供建议、纠错、催更等加作者微信: gairuo123(备注:pandas教程)和关注公众号「盖若」ID: gairuo。跟作者学习,请进入 Python学习课程。欢迎关注作者出版的书籍:《深入浅出Pandas》 和 《Python之光》。
Python 3.10 实现的 match 功能是其他开发语言中传统的 switch 的超集,它不仅仅是 switch,可以实现更为复杂的功能。模式匹配大大增加了控制流的清晰度和表达能力。
虽然使用嵌套的“if”语句的“命令性”系列指令可以被用来完成类似结构化模式匹配的效果,但它没有“声明性”方式那样清晰。相反地,“声明性”方式指定了一个匹配所要满足的条件,并且通过其显式的模式使之更为易读。更强大的模式匹配例子可以在 Scala 和 Elixir 等语言中找到。 这种结构化模式匹配方式是“声明性”的并且会显式地为所要匹配的数据指定条件(模式)。
一直以来,Python 没有其他语言的 switch 方法来实现多条件分支,要求支持的呼声很高,Python 3.10.0 支持了它,而且是超级版的,实现的思路与它们大有不同。match 与 case 配合,由 case 从上到下将目标与语句中的每个模式进行比较,直到确认匹配,执行相应的模式下的语句。如果未确认完全匹配,则最终用通配符 _
(如提供)将用作匹配情况。如所有的都不匹配且没有通配符,则相当于空操作。
模式由序列、映射、基本数据类型以及类实例构成。 模式匹配使得程序能够从复杂的数据类型中提取信息、根据数据结构实现分支,并基于不同的数据形式应用特定的动作。
采用模式加上相应动作的 match 语句 和 case 语句 的形式的结构化模式匹配。 其中 match 与 case 在特点句法下为 Python 的软关键字(Soft keywords):
match subject:
case <pattern_1>:
<action_1>
case <pattern_2>:
<action_2>
case <pattern_3>:
<action_3>
case _:
<action_wildcard>
match 语句接受一个表达式并将其值与以一个或多个 case 语句块形式给出的一系列模式进行比较。 具体来说,模式匹配的操作如下:
详细语法:
match_stmt: "match" subject_expr ':' NEWLINE INDENT case_block+ DEDENT
subject_expr:
| star_named_expression ',' star_named_expressions?
| named_expression
case_block: "case" patterns [guard] ':' block
guard: 'if' named_expression
注意,对 subject 的匹配不仅为具体值,还可以包括它的数据结构,可以匹配到全部结构和部分结构内容,并将这些匹配到的结构内容引用到关联的处理动作中作为变量。具体的理解可以看看下方的用法示例。另外:
[x, y, *rest]
和 (x, y, *rest)
的作用类似于解包赋值中的通配符*
之后的名称也可以为 _
,因此 (x, y, *_)
可以匹配包含两个条目的序列而不必绑定其余的条目{"bandwidth": b, "latency": l}
会从一个字典中捕获 "bandwidth"
和 "latency"
值,与序列模式不同,额外的键会被忽略。 也支持通配符 **rest
。(但 **_
是冗余的,因而不被允许)结构化模式匹配可以采取将一个变量与一个 case 语句中的字面值进行比较的最简单形式来使用,但 Python 的模式匹配真正针对的目标类型和形状的处理操作。接下来就用一些例子来说明:
这是最简单的应用,一个值,即主词,被匹配到几个字面值,即模式。在下面的例子中,status 是匹配语句的主词。模式是每个 case 语句,字面值代表请求状态代码。匹配后,将执行与该 case 相关的动作:
grade = 3
match grade:
case 1:
print('一年级')
case 2:
print('二年级')
case 3:
print('三年级')
case _:
print('未知年级')
# 三年级
变量名 _
作为 通配符 并确保目标将总是被匹配,是可选的,如果没有又之前 case 未得到匹配,则会执行一个空操作(no-op)。还可以用 | (表示或者)在一个模式中组合几个字面值:
grade = 5
match grade:
case 1:
print('一年级')
case 2:
print('二年级')
case 3:
print('三年级')
case 4 | 5 | 6:
print('高年级')
case _:
print('未知年级')
# 高年级
下边是一利用一个类状态实现的开关功能:
class switch:
on = 1
off = 0
status = 0
match status:
case switch.on :
print('Switch is on')
case switch.off :
print('Switch is off')
可以在 case 中编写 if 条件语句,实现与 if 语句类似的功能:
score = 81
match score:
case 100:
print('满分!')
case score if score >= 80:
print(f'高分啊~ {score}')
case score if score >= 60:
print('及格万岁!')
case _:
print('不知道说什么。')
# 高分啊~ 81
在 case 后面可以加入一个 if 判断作为守卫,如匹成功但守卫为假,则继续尝试下一个 case 案例块,值捕获发生在评估守卫之前。再来一个例子:
def fun(score):
match score:
case 100:
print('满分!')
case score if score >= 80:
print(f'高分啊~ {score}')
case score if score >= 60:
print('及格万岁!')
case score if score in [57, 58, 59]:
print('这成绩也是没谁了 ··!')
case _:
print('不知道说什么。')
fun(59)
# 这成绩也是没谁了 ··!
模式可以看起来像解包形式,而且模式可以用来绑定变量。在这个例子中,一个数据点可以被解包为它的 x 坐标和 y 坐标:
# point is an (x, y) tuple
match point:
case (0, 0):
print("坐标原点")
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("未法的坐标数据")
第一个模式有两个字面值 (0, 0) ,可以看作是上面所示字面值模式的扩展。接下来的两个模式结合了一个字面值和一个变量,而变量 绑定 了一个来自主词的值(point)。 第四种模式捕获了两个值,这使得它在概念上类似于解包赋值 (x, y) = point 。
这种情况也可以增加守卫:
point = (60, 0)
match point:
case (0, 0):
print("坐标原点")
case (0, y):
print(f"Y={y}")
case (x, 0) if x > 50:
print(f"X={x},点在 x 轴的远处")
case (x, 0):
print(f"X={x}")
case (x, y):
print(f"X={x}, Y={y}")
case _:
raise ValueError("未法的坐标数据")
# X=60,点在 x 轴的远处
模式可以使用命名的常量,且必须使用 .
以防止被解释为捕获变量:
from enum import Enum
class Color(Enum):
RED = 0
GREEN = 1
BLUE = 2
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 :(")
某些字面量会被特殊对待,例如 None, False, True,相应于 is 完成匹配:
match b:
case True:
print("Yes!")
# 相当于:
...
if b is True:
print("Yes!")
通过类对象可以结构化你的数据,通过使用类名字后面跟一个类似构造函数的参数列表,作为一种模式。这种模式可以将类的属性捕捉到变量中:
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=x, y=y):
print(f"X={x}, Y={y}")
case Point():
print("这个点不在轴上")
case _:
raise ValueError("未法的坐标数据")
还可以匹配取值:
match media_object:
case Image(type=media_type):
print (f"Image of type {media_type}")
另外一个例子:
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)
direction(d2)
direction(d3)
还可以利用星号表达式进行解包操作:
# 解析出列表
for thing in [[1,2,3],['a','b','c'],"this won't be matched"]:
match thing:
case [*y]:
print(y)
case _:
print("unknown")
'''
[1, 2, 3]
['a', 'b', 'c']
unknown
'''
匹配数据类型:
for thing in [[1, 2, 3], ['a', 'b', 'c'], "this won't be matched"]:
match thing:
case [int(), int(second), int()] as y:
print(y, f'第二个是{second}')
case _:
print("unknown")
'''
[1, 2, 3] 第二个是2
unknown
unknown
'''
你可以在某些为其属性提供了排序的内置类(例如 dataclass)中使用位置参数。 你也可以通过在你的类中设置 __match_args__
特殊属性来为模式中的属性定义一个专门的位置。 如果它被设为 ("x", "y"),则以下模式均为等价的(并且都是将 y 属性绑定到 var 变量):
Point(1, var)
Point(1, y=var)
Point(x=1, y=var)
Point(y=var, x=1)
类中设置 __match_args__
特殊属性为:
class Point:
__match_args__ = ("x", "y")
def __init__(self, x, y):
...
模式可以任意地嵌套。 例如,如果我们的数据是由点组成的短列表(类似 [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("列表中还有其他内容")
通配符可以被用在更复杂的模式中,例如 ('error', code, _)
。 举例来说:
match test_variable:
case ('warning', code, 40):
print("A warning has been received.")
case ('error', code, _):
print(f"An error {code} occurred.")
在上述情况下,test_variable 将可匹配 ('error', code, 100)
和 ('error', code, 800)
。
为了匹配字典,我们对每种情况使用 key:value
模式。在这里,我们检查消息是“成功”还是“失败”,并打印相应的消息:
def check_message(message):
match message:
case {'success': message}:
print(f'Success: {message}')
case {'failure': message}:
print(f'Something wrong: {message}')
case _:
print('Unknown')
message_success = {'success': 'OK!'}
message_failure = {'failure': 'ERROR!'}
check_message(message_success)
check_message(message_failure)
'''
Success: OK!
Something wrong: ERROR!
'''
子模式可使用 as 关键字来捕获,如 case (Point(x1, y1), Point(x2, y2) as p2): ...
。下例:
def alarm(item):
match item:
case [('早上' | '中午' | '晚上') as time, action]:
print(f'{time}好! 是{action}的时间了!')
case _:
print('不是时间!')
alarm(['早上', '吃早餐'])
# 早上好! 是吃早餐的时间了!
可以作为独立语句使用:
thing = 2
match thing:
case 1:
print("thing is 1")
case 2:
print("thing is 2")
case _:
print("thing is other")
可以用用在 for 循环中:
for thing in [1,2,3,4]:
match thing:
case 1:
print("thing is 1")
case 2:
print("thing is 2")
case _:
print("thing is other")
可以用在函数方法中:
def whats_that(thing):
match thing:
case 1:
print("thing is 1")
case 2:
print("thing is 2")
case _:
print("thing is other")
可以嵌套:
def alarm(item):
match item:
case [time, action]:
print(f'Good {time}! It is time to {action}!')
case [time, *actions]:
print('Good morning!')
for action in actions:
print(f'It is time to {action}!')
match 语句将 subject 表达式 与 case 语句中的每个模式(pattern)从上到下进行比较,直到找到匹配的模式。若找不到匹配的表达式,则匹配 _ 通配符(wildcard)(如有),实际上它就是其它语言中的 default 关键字。
支持的有:
_
不作为变量名,是一种特殊的模式,它始终匹配但从不绑定值其他:
|
表示或if
条件再判断(守卫)as
绑定值*
或 **
和通配符使用[*_]
匹配任意长度的 list;(_, _, *_)
匹配长度至少为 2 的 tuple。Sandwich(cheese=_)
检查匹配的对象是否具有属性 cheese更新时间:2021-11-24 08:09:14 标签:python match case