python:结构化模式匹配 match case

背景

  • 一直以来,Python 没有其他语言的 switch 方法来实现多条件分支,要求支持的呼声很高,Python 3.10.0 支持了它,而且是超级版的,实现的思路与它们大有不同。match 与 case 配合,由 case 从上到下将目标与语句中的每个模式进行比较,直到确认匹配,执行相应的模式下的语句。如果未确认完全匹配,则最终用通配符 _(如提供)将用作匹配情况。如所有的都不匹配且没有通配符,则相当于空操作。

  • 模式由序列、映射、基本数据类型以及类实例构成。 模式匹配使得程序能够从复杂的数据类型中提取信息、根据数据结构实现分支,并基于不同的数据形式应用特定的动作。

  • Python 3.10 实现的 match 功能是其他开发语言中传统的 switch 的超集,它不仅仅是 switch,可以实现更为复杂的功能。模式匹配大大增加了控制流的清晰度和表达能力。

语法

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 代码块不执行任何操作

用法

匹配一个字面值

grade = 3
match grade:
    case 1:
        print('一年级')
    case 2:
        print('二年级')
    case 3:
        print('三年级')
    case _:
        print('未知年级')

# 三年级

  • 匹配后,将执行与该 case 相关的动作:
  • 变量名 _ 作为 通配符 并确保目标将总是被匹配,是可选的, 如果之前没有匹配到,那么会执行这个语句

还可以用 | (表示或者)在一个模式中组合几个字面值:

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')

守卫 Guard

可以在 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("未法的坐标数据")

这种情况也可以增加守卫:

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 :(")

模式和类

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

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.")

匹配字典

为了匹配字典,我们对每种情况使用 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

子模式可使用 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 关键字。

支持的有:

  • 字面常数(literal):如 int、float、string、bool 和 None,不支持 f-string 不是字面常数
  • 捕获模式:用匹配表达式赋值目标,一个名称最多只能绑定一次
  • 通配符模式:_ 不作为变量名,是一种特殊的模式,它始终匹配但从不绑定值
  • 常量值模式:属性(color.RED)和枚举类(enum.Enum)
  • 序列模式
  • 映射模式
  • 类模式

其他:

  • | 表示或
  • 加 if 条件再判断(守卫)
  • 子模式可加 as 绑定值
  • list、tuple、dict 等都能作为模式,并且能配合 * 或 ** 和通配符使用
  • [*_] 匹配任意长度的 list;
  • (_, , *) 匹配长度至少为 2 的 tuple。
  • 类模式:Sandwich(cheese=_) 检查匹配的对象是否具有属性 cheese ???
  • 5
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值