match-case的使用

Python实用教程_spiritx的博客-CSDN博客

python没有switch-case语句,但在Python 3.10 增加了 match-case 的条件判断,不需要再使用一连串的 if-else 来判断了,使用上比switch-case更为强大,match-case是一种结构匹配模式,与switch-case也有许多的差异,所以单列一章进行学习。

简单用法

match subject:
    case <pattern_1>:
        <action_1>
    case <pattern_2>:
        <action_2>
    case <pattern_3>:
        <action_3>
    case _:
        <action_wildcard>

case _: 类似于 C 和 Java 中的 default:,当其他 case 都无法匹配时,匹配这条,保证永远会匹配成功。

标量

标量是指case后面的匹配值是标量。

标量的用法与C、Java的用法基本相同。

注意,这里的标量只能是常量,以及与常量相当的枚举值,变量是不能作为case后面的匹配值使用的,变量在match-case中有特殊的作用,这一点与其他语言的switch是有很大的差异。

error_code = 500

def http_error(status):
    match status:
        case 400:
            return "Bad request"
        case 404:
            return "Not found"
        case 418:
            return "I'm a teapot"
        #case error_code:   #这个是错误的,不允许使用任何变量
        #    return "HTTP-Internal Server Error!"
        case _:
            return "Something's wrong with the internet"

mystatus=400
print(http_error(400))

def capture(greeting):
    match greeting:
        case '':
            print("greeting is null!")
        case 'Jack':
            print('Hello Jack!')
        # case name:  #会提示 SyntaxError: name capture 'name' makes remaining patterns unreachable
        #     print(f'Hi {name}!')
        case _:
            print('other!')

capture('Jack')
capture('John')
capture('')

'''
Hello Jack!
other!
greeting is null!
'''

多值 

一个 case 也可以设置多个匹配条件,条件使用 | 隔开,例如:

...
    case 401|403|404:
        return "Not allowed"

注意:与C和Java不一样,多值不是使用逗号分割,而是使用 | 隔开,逗号在match-case中另有用处。

match-case只有OR模式,没有AND模式

枚举

可以通过定义枚举值来进行条件匹配,match-case看上去是把枚举值当做标量来处理。

class Color():
    RED = 1
    GREEN = 2
    BLUE = 3

class NewColor:
    YELLOW = 4

class Black():
    BLACK =5

def constant_value(color):
    match color:
        case Color.RED:
            print('RED')
        case NewColor.YELLOW:
            print('YELLOW')
        case other:
            print(f'{other=}')


constant_value(Color.RED)
constant_value(Color.GREEN)
constant_value(NewColor.YELLOW)
constant_value(4)

'''
RED
other=2
YELLOW
YELLOW
'''

条件

case后面可以是一个条件表达式,如:

score = 81
match score:
    case 100:
        print('满分!')
    case score if score >= 80:
        print(f'高分啊~ {score}')
    case score if score >= 60:
        print('及格万岁!')
    case _:
        print('不知道说什么。')

# 高分啊~ 81

高级用法

元组匹配

元组匹配时,可以匹配整个元组常量,也可以匹配元组的一部分常量,另一部使用变量代替。

代替的变量部分会被match-case赋值,是一种基于位置的绑定变量,这是match-case所特有的特性,对于不想取值的变量,可以使用"_"进行替代,对于多个可以使用"*"作为通配符进行替代,如下例子:

def printPoint(point):
    match point:
        case (0, 0):
            print("坐标原点")
        case (0, y):        #绑定变量y,第一个元素为0就匹配,并且只有2个元素
            print(f"Y={y}")
        case (0, 0, z):     #绑定变量z,第一、二个元素都是0,并且要三个元素的元组
            print(f"Z={z}")
        case (x, 0) if x > 30:
            print(f"X={x} and x > 30")
        case (x, 0):        #绑定变量x,第二个元素为0就匹配
            print(f"X={x}")
        case (a, 10):   #绑定变量是a,变量名任意都可以
            print(f"A2={a}")
        case (10,_):
            print(f"10_")
        case (x, y):
            print(f"X={x}, Y={y}")
        case (a,10):        #这个永远不会被执行,因为被前面的(x,y)给拦截了
            print(f"A2={a}")
        case _:
            print("other!")

printPoint((0,0))
printPoint((40,0))
printPoint((20,0))
printPoint((55,10))
printPoint((10,23))
printPoint((0,0,1))
printPoint((11,12))

'''
坐标原点
X=40 and x > 30
X=20
A2=55
10_
Z=1
X=11, Y=12
'''

列表匹配

与元组匹配是一样的,并且二者可以混用:

def alarm(item):
    match item:
        case [time, action]:
            print(f'Good {time}!It is time to {action}!')
        case _:
            print('other!')


alarm(['morning', 'work'])
alarm(('afternoon', 'have breakfast', 'brush teeth', 'work’))

'''
Good morning!It is time to work!
other!
'''

元组、列表去括号和混用

元组和列表也可以去括号,直接使用逗号分隔开,并且即使有括号,二者也是可以混用的

def sequence(collection):
    match collection:
        case 1, [x, *others]: #类型和结构匹配,常量匹配,变量赋值,这种是去掉括号的写法
            print(f'Got 1 and a nested sequence:{x=},{others=}')
        case (1,x): #即使是写成元组的匹配模式,也是可以匹配列表的
            print(f'Got 1 and {x}')
        case [x,y,z]: #也可以去掉括号,效果是一样的,可以同时匹配元组和列表
            print(f'{x=},{y=},{z=}')
        case a,b,c,d:#去掉括号的模式
            print(f'{a=},{b=},{c=},{d=}')
        case _:
            print("not captrue!")
sequence((1,10))
sequence([1,2])
sequence([10,20,30])
sequence((11,21,31)) #匹配规则虽然写的是列表,但同样也能匹配元组
sequence((1, [2,3,4,5,6]))
sequence((1,2,3,4))
sequence([10,20,30,30])

'''
Got 1 and 10
Got 1 and 2
x=10,y=20,z=30
x=11,y=21,z=31
Got 1 and a nested sequence:x=2,others=[3, 4, 5, 6]
a=1,b=2,c=3,d=4
a=10,b=20,c=30,d=30
'''

字典匹配

字典匹配的键必须是常量,值的部分可以通过变量绑定获取。另外可以通过"**valuename"来作为通配符匹配除已经匹配外的所有其他字典

注意的是,在字典匹配中,只要是含有待匹配的模式,都会被匹配,可以有多余的键值对,这点与前面的列表和元组不同,并且键值对的顺序也没有要求。

**valuename也不是匹配后面的字典,而是除已经匹配外的所有其他字典

def mapping(mp):
    match mp:
        case {'k1':'v1'}:   #有k1-v1的字典都会被匹配
            print(f'k1:v1, {mp}')
        case {'k21':'v21', 'k22':route}:
            print(f'k22:{route=}, {mp}')
        case {'k31':v31,**rest}:  #**rest只能放在结尾
            print(f'k31:{v31=},{rest=}')
        case {'k41':_}:
            print(f'k41')
        case {'k51':'v51', 'k52':_, **rest }:
            print(f'k51, {rest}')
        case _:
            print('null')

mapping({})
mapping({'k1':'v1'})
mapping({'k1':'v1', 'k2':'v2'})  #只要有匹配就行,可以有多余的键值
mapping({'r1':'r1', 'r2':'r2', 'k1':'v1'}) #键值的顺序无关
mapping({'k21':'v21', 'k22':'female'})
mapping({'r1':'r1', 'k21':'v21', 'r2':'r2', 'k22':'female', 'r3':'r3'}) #可以有多余的,顺序无关
mapping({'k31':'Rose', 'login':'root', 'path':'/root/var', 'user':'app'}) #**是把匹配的部分之外的全部获取
mapping({'r1':'v1', 'k31':'Rose', 'r2':'v2', 'login':'root', 'path':'/root/var', 'user':'app'}) #**是把匹配之外的全部获取,包括在匹配键值前面的
mapping({'r1':'v1', 'k51':'v51', 'r2':'v2', 'k52':'v52', 'login':'root', 'path':'/root/var', 'user':'app'}) #**是把匹配之外的全部获取,包括在匹配键值前面的

mapping({'k41':'John'})
mapping({'k41':'John', 'sex':0})

'''
null
k1:v1, {'k1': 'v1'}
k1:v1, {'k1': 'v1', 'k2': 'v2'}
k1:v1, {'r1': 'r1', 'r2': 'r2', 'k1': 'v1'}
k22:route='female', {'k21': 'v21', 'k22': 'female'}
k22:route='female', {'r1': 'r1', 'k21': 'v21', 'r2': 'r2', 'k22': 'female', 'r3': 'r3'}
k31:v31='Rose',rest={'login': 'root', 'path': '/root/var', 'user': 'app'}
k31:v31='Rose',rest={'r1': 'v1', 'r2': 'v2', 'login': 'root', 'path': '/root/var', 'user': 'app'}
k51, {'r1': 'v1', 'r2': 'v2', 'login': 'root', 'path': '/root/var', 'user': 'app'}
k41
k41
'''

通配符

  • _    匹配一个序列和元组中的任意一个元素,匹配一个字典中的任意值
  • *_  匹配元组或序列中的任意个(可以是0个)的元素
  • *valuename  匹配元组或序列中的一个和多个任意的元素,并且将这些元素以列表赋值给绑定变量valuename
  • **valuename  用于匹配字典中除已经匹配的字典外的所有字典,将这些字典赋值给绑定变量valuename。

def sequence(collection):
    match collection:
        case 1,x:
            print(f'Got 1 and {x}')
        case _,_: #匹配两个元素的列表或元组
            print(f'Some pair')
        case 'Jack', *_, "John": #放中间也可以,至少有一个中间的
            print('matches Jack ... John!')
        case 'Jack', *_:
            print('match Jack...')
        case 'Mike',_, *other:
            print(f'matches Mike ...{other}') #输出的other一定是列表
        case {'name':name, 'sex':_, **rest}:
            print(f'match dict, {name}, {rest}')
        case _:
            print("not captrue!")

sequence((1,20))
sequence((10,20))
sequence(('Jack'))
sequence(['Jack', 'John'])
sequence(('Jack', 'Rose', 'Cui', 'Chen'))
sequence(('Jack', 'Rose', 'Cui', 'Chen', 'John'))
sequence(['Mike', 'English', 'Country', 'Rose'])
sequence(('Mike', 'English', 'Country', 'Rose'))
sequence(('Mike', 'English', 'Rose'))
sequence({'sex':'female', 'age':25, 'city':'YORK', 'name':'Rose'})

'''
Got 1 and 20
Some pair
not captrue!
Some pair
match Jack...
matches Jack ... John!
matches Mike ...['Country', 'Rose']
matches Mike ...['Country', 'Rose']
matches Mike ...['Rose']
match dict, Rose, {'age': 25, 'city': 'YORK'}
'''

类型判断

match-case可以对基础类型、不需要构建参数的自定义类做类型判断

def type_pattern(obj):
    match obj:
        case list():
            print('list')
        case set():
            print('set')
        case str():
            print('string')
        case bytes():
            print('bytes')
        case int():
            print('int')
        case tuple():
            print('tuple')
        case dict():
            print('dict')
        case MyClass():
            print('MyClass')
        case _:
            print('other')

type_pattern(1)
type_pattern('My GOD!')
type_pattern((1,2,3,4,5))
type_pattern([1,2,3,4,5,6])
type_pattern({'k1':10, 'k2':20})
type_pattern(MyClass())

'''
int
string
tuple
list
dict
MyClass
'''

自定义类匹配

无构建参数

对于无构建参数的自定义类,可以使用match-case直接进行匹配,但是,它们只是做了类型匹配,并没有比较二者的“值”是否相同

class MyClass1:
    def __init__(self):
        self.value = 0

    def set(self, value):
        self.value = value

    def get(self):
        return self.value

class MyClass2:
    def __init__(self):
        self.value = 0

    def set(self, value):
        self.value = value

    def get(self):
        return self.value

def class_pattern(cp):
     match cp:
         case MyClass1():  #只是做了类型上的匹配
             print(f'MyClass1 match! {cp.get()}')
         case MyClass2():
             print(f'MyClass2 match! {cp.get()}')
         case _:
            print('other!')

c11 = MyClass1()
c11.set(10)
c12 = MyClass1()
c12.set('John')
c21 = MyClass2()
c21.set(20)
class_pattern(c11)
class_pattern(c12)
class_pattern(c21)

'''
MyClass1 match! 10
MyClass1 match! John
MyClass2 match! 20
'''

子类

所有的子类可以匹配父类

class MyClass1:
    def __init__(self):
        self.value = 0

    def set(self, value):
        self.value = value

    def get(self):
        return self.value


class MyClass11(MyClass1):
    pass

def class_pattern(cp):
     match cp:
         case MyClass1():  #只是做了类型上的匹配
             print(f'MyClass1 match! {cp.get()}')
         case _:
            print('other!')

c11 = MyClass1()
c11.set(10)
class_pattern(c11)
cc1 = MyClass11()
cc1.set(100)
class_pattern(cc1)
'''
MyClass1 match! 10
MyClass1 match! 100
'''

有构建参数

对于有构建参数自定义类,需要指定参数值,否则会报错

def pair_pattern(np):
    match np:
        case NewPair(x=10,y=20):
            print(f'NewPair')
        #case NewPair(x,y):  #会报错误 TypeError: NewPair() accepts 0 positional sub-patterns (2 given)
            print(f'NewPair,{x=},{y=}')
        case _:
            print("other!")

pair_pattern(NewPair(10,20))
pair_pattern(NewPair(1,2))

'''
NewPair
other!
'''

并且在匹配时,会匹配构建参数是否相等,只有参数相等才会匹配,但对于不在构建函数中的自有变量不会去做匹配

class NewPair:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.z = 0

    def add(self):
        self.x += 1
        self.y += 2
        self.z += 1

    def sub(self):
        self.x -= 1
        self.y -= 2
        self.z += 1

    def __str__(self):
        return f'{self.x=}, {self.y=}, {self.z}'

def pair_pattern(np):
    match np:
        case NewPair(x=10,y=20):
            print(f'NewPair, {np}')
        #case NewPair(x,y):  #会报错误 TypeError: NewPair() accepts 0 positional sub-patterns (2 given)
        #    print(f'NewPair,{x=},{y=}')
        case _:
            print(f"other! , {np}")
c1 = NewPair(10,20)
c2 = NewPair(10,20)
pair_pattern(c1)
pair_pattern(c2)
c2.add()
pair_pattern(c2)
c2.sub()
pair_pattern(c2)
pair_pattern(NewPair(1,2))


'''
NewPair, self.x=10, self.y=20, 0
NewPair, self.x=10, self.y=20, 0
other! , self.x=11, self.y=22, 1
NewPair, self.x=10, self.y=20, 2
other! , self.x=1, self.y=2, 0
'''

参数绑定

也可以和元组、列表一样对构建参数设置绑定,只做类型匹配,然后将参数取出来。有两种方法来实现,第一种方法比较优雅,这个问题主要是因为对于自定义类,解释器无法确定参数顺序,所以需要指定参数顺序,在构造时既然可以指定参数为常量,当然可以指定参数为变量,甚至可以使用通配符“_”

如下:

class NewPair:
    def __init__(self, x, y):
        self.x = x
        self.y = y


def pair_pattern(np):
    match np:
        case NewPair(x=10,y=20):
            print(f'1.NewPair')
        case NewPair(x=11, y=y1): #匹配x=11的自定义类,绑定y的值到y1中
            print(f'2.NewPair,x=11,y={y1}')
        case NewPair(x=_, y='Female'):
            print(f'3.NewPair,x=_,y="Female"')
        case NewPair(x=x1,y=y1):  #将x、y的值全部绑定到x1,y1中
            print(f'4.NewPair,x={x1},y={y1}')
        case _:
            print("other!")

pair_pattern(NewPair(10,20))
pair_pattern(NewPair(11,21))
pair_pattern(NewPair(100,'Female'))
pair_pattern(NewPair('Jack','Rose'))

‘’'
1.NewPair
2.NewPair,x=11,y=21
3.NewPair,x=_,y="Female"
4.NewPair,x=Jack,y=Rose
‘''

上面的方法非常优雅,另外网友还提供了一种方法,通过在类定义中声明__match_args__指定参数顺序,也是可以的,但这种方法在理解上会造成一些困惑:

class NewPair1:
    __match_args__ = ('x','y')
    def __init__(self, x, y):
        self.x = x
        self.y = y

class NewPair2:
    __match_args__ = ('x', 'y')
    def __init__(self, x, y):
        self.x = x
        self.y = y


def pair_pattern(np):
    match np:
        case NewPair1(x,y):
            print(f'NewPair1,{x=},{y=}')
        case NewPair2(x,y):
            print(f'NewPair2,{x=},{y=}')

pair_pattern(NewPair1('Jack', 22))
pair_pattern(NewPair1('Rose', 122))
pair_pattern(NewPair2('English', 'Chinese'))

'''
NewPair1,x='Jack',y=22
NewPair1,x='Rose',y=122
NewPair2,x='English',y='Chinese'
'''

如果构建参数有多个,而我们只需要提取其中的几个,可以只在__match_args__中声明一部分,在case后面加上要提取的参数就可以了

class NewPair1:
    __match_args__ = ('x','y')
    def __init__(self, a, x, y, b):
        self.x = x
        self.y = y
        self.a = a
        self.b = b

class NewPair2:
    __match_args__ = ('x', 'y')
    def __init__(self, x, y):
        self.x = x
        self.y = y


def pair_pattern(np):
    match np:
        case NewPair1(x,y): #匹配参数是构建参数的子集
            print(f'NewPair1,{x=},{y=}')
        case NewPair2(x,y):
            print(f'NewPair2,{x=},{y=}')

pair_pattern(NewPair1('argv1', 'Jack', 22, 1))
pair_pattern(NewPair1('argv1','Rose', 122, 2))
pair_pattern(NewPair2('English', 'Chinese'))

'''
NewPair1,x='Jack',y=22
NewPair1,x='Rose',y=122
NewPair2,x='English',y='Chinese'
'''

AS模式

因为match-case没有AND模式,如果又要判断又要取值,就需要用到AS模式,AS后面就是跟上变量绑定。

可以通过这种方式匹配局部的类型,并且进行值的绑定,部分的实现AND效果

def type_pattern2(obj):
    match obj:
        case int() as level:
            print(f'int, {level=}')
        case set() as s1:
            print(f'set, {s1=}')
        case str() as name:
            print(f'string, {name=}')
        case bytes():
            print('bytes')
        case 'Jack', int() as level, 'female':  #先判断局部的类型,再绑定
            print(f'Jack, {level=}')
        case 'Rose', str() as name, 1: #先判断局部的类型,再绑定
            print(f'Rose, {name=}')
        case 'Mike', tuple() as tp, 1: #先判断局部的类型,再绑定,多层嵌套也是可以的
            print(f'Mike, {tp=}')
        case 'John', {'k1':str() as v1}, (_, _, int() as v2, *_):
            print(f'John, {v1=}, {v2=}')
        case list() as l1:
            print(f'list, {l1=}')
        case dict() as d1:
            print(f'dict, {d1=}')
        case _:
            print('other')

type_pattern2(10)
type_pattern2('My GOD!')
type_pattern2(('Jack',30,'female'))
type_pattern2(['Mike', (10,11,12,13), 1])
type_pattern2(['Jack',30,'female'])
type_pattern2(['Rose','vv',1])
type_pattern2(['John', {'k11':'v11', 'k12':'v12', 'k1':'ooo'}, (100, '101', 103, 'aa', 'bb')])

'''
int, level=10
string, name='My GOD!'
Jack, level=30
Mike, tp=(10, 11, 12, 13)
Jack, level=30
Rose, name='vv'
John, v1='ooo', v2=103
'''

组合使用

高级和简单方法组合使用,就可以组成非常强大的match-case

def pattern1(obj):
    match obj:
        case ['go', direction] if direction in ['east', 'north']:
            print('GO Right!')
        case direction if direction == 'west':
            print('GO Wrong!')
        case ['go', _] | _:
            print('Other way!')

pattern1(['go', 'east'])
pattern1('west')
pattern1('north')
pattern1(['go', 'west'])

'''
GO Right!
GO Wrong!
Other way!
Other way!
'''