match-case的使用
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!
'''