该特性已经有 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, FalseNone 则是按标识号比较的。

使用 | 在一个模式中还可以组合多个字面值:

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

本文来源:https://blog.csdn.net/dreaming_coder/article/details/131418193

声明:如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。None#python87.com