1.問題引入
? ? 假設(shè)我們有這樣一個動物類,示例代碼如下所示:
from typing import Optional
class Animal():
def __init__(self, name:str):
self.name = name
def __str__(self)->str:
return self.name.upper()
class Cat(Animal):
pass
class Dog(Animal):
pass
class Tiger(Animal):
pass
? ? 現(xiàn)在有這樣一個需求,根據(jù)傳入的動物名稱,調(diào)用不同的類進(jìn)行初始化,我們可以有以下幾種方式來處理。
1.1 使用if語句
? ? 在Python中,在進(jìn)行多分支判斷時,使用最多的就是if-elif-else。示例代碼如下所示:
def animal_factory_use_if(animal_type:str)->Optional[Animal]:
if animal_type.lower() == "cat":
return Cat(name="Cat")
elif animal_type.lower() == "dog":
return Dog(name="Dog")
elif animal_type.lower() == "tiger":
return Tiger(name="Tiger")
else:
return None
1.2 使用字典
? ? 除了使用if語句,我們也可以使用字典,示例代碼如下所示:
def animal_factory_use_dict(animal_type:str)->Optional[Animal]:
# 構(gòu)造字典數(shù)據(jù)
animal_dict={
"cat":Cat(name="Cat"),
"dog":Dog(name="Dog"),
"tiger":Tiger(name="Tiger")
}
return animal_dict.get(animal_type.lower())
1.3 使用match
? ? 對于這種多分支判斷情況,其他編程語言,例如Java、C#、Go等,除if語句之外,還提供了switch-case結(jié)構(gòu),那Python有沒有類似的結(jié)構(gòu)來實現(xiàn)類似的功能呢?
? ? 其實在Python 3.10 版本中提供了 match-case 特性,先看看示例代碼如下所示:
def animal_factory_use_match_case(animal_type:str)->Optional[Animal]:
match animal_type.lower():
case "cat":
return Cat(name="Cat")
case "dog":
return Dog(name="Dog")
case "tiger":
return Tiger(name="Tiger")
case _:
return None
? ? match 后面跟要匹配的變量,case跟不同的條件,再之后跟符合條件的執(zhí)行語句,最后一個下劃線表示缺省匹配,如果前面的case都沒有匹配的項,就執(zhí)行這個case,相當(dāng)于之前的else。是不是跟Java、C#、Go語言里面的 switch-case 很像? 但match-case能做的事情是超過switch-case的。它支持更加復(fù)雜的模式匹配。讓我們來一起學(xué)習(xí)吧。
2. match-case
2.1 概述
? ? 為解決前面提到的問題,在Python 3.10中引入了非常強(qiáng)大的match-case語法,也稱為結(jié)構(gòu)模式匹配(Structural Pattern Matching),這是一種全新的流程控制語句,允許根據(jù)值的結(jié)果執(zhí)行不同的代碼塊。
2.2 基本概念
2.2.1 match語法
? ? match-case語法類似于其他編程語言的switch語句,但功能更為強(qiáng)大,允許根據(jù)值的結(jié)果執(zhí)行不同的代碼塊。
2.2.2 為什么使用match
- 簡潔性:使得多重條件判斷更加清晰和易讀
- 可擴(kuò)展性:支持復(fù)雜的結(jié)構(gòu)和類型匹配
- 功能強(qiáng)大:可對序列、字典、類等進(jìn)行解構(gòu)和匹配
2.2.3 語法結(jié)構(gòu)
? ? 基本語法結(jié)構(gòu)如下所示:
match value:
case pattern1:
# 執(zhí)行代碼塊-1
case pattern2:
# 執(zhí)行代碼塊-2
case _:
# 執(zhí)行默認(rèn)代碼塊
-
match value: 要進(jìn)行匹配的值 -
case pattern: 匹配的模式 -
case _: 匹配任何值,相當(dāng)提前預(yù)設(shè)一種場景
3. 基本使用
3.1 字面值模式
? ? 在字面值模式中,可以使用Python自帶的數(shù)據(jù)結(jié)構(gòu),如字符串、數(shù)字、布爾值和None等,示例代碼如下所示:
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 匹配成功,則不會執(zhí)行任何分支
3.2 多模式匹配
? ? 可以使用 |(表示或)在一個模式中組合幾個字面值,示例代碼如下所示:
def http_error_combine(status):
match status:
case 401 | 403 | 404:
return "Not allowed"
case 200 | 202 :
return "request successful"
case 301 | 302 | 304:
return "redirect"
case 500 | 502:
return "server error"
case _:
return "Something's wrong with the internet"
3.3 序列模式
? ? 在match-case中,也可使用解包賦值的方式來綁定變量,示例代碼如下所示:
def print_point(point:Tuple[int])->str:
match point:
case (0, 0):
print("Origin")
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("Not a point")
? ? 以上功能詳細(xì)解釋如下所示:
- (0, 0):表示有兩個字面值,可以理解為字面值模式的擴(kuò)展
- (0, y)、(x, 0):表示結(jié)合了一個字面值模式和一個變量,而變量則是綁定了point的一值
- (x, y):表示捕獲了兩個值,在概念與解包賦值相同,即(x, y) = point
? ? 除了在match-case中使用元組之外,還可以使用列表,示例代碼如下所示:
def match_sequence(sequence:Sequence):
match sequence:
case [1,2]:
print(f"條件:[1,2] 輸入{sequence} 結(jié)果:{[1,2]}")
case [1,(x,y)]:
print(f"條件:[1,(x,y)] 輸入{sequence} 結(jié)果:x={x},y={y}")
case [x,y,z]:
print(f"條件:[x,y,z] 輸入{sequence} 結(jié)果:x={x},y={y},z={z}")
case [x,*y,z]:
print(f"條件:[x,*y,z] 輸入{sequence} 結(jié)果:x={x},y={y},z={z}")
case [x,[*y,z]]:
print(f"條件:[x,[*y,z]] 輸入{sequence} 結(jié)果:x={x},y={y},z={z}")
case _:
print("Not a sequence")
if __name__ == "__main__":
match_sequence([1,2])
match_sequence([1,(2,3)])
match_sequence([1,{4,5}])
match_sequence([1,2,3])
match_sequence([1,2,3,4,5])
match_sequence([1,100,99,[2,3,4,5],9999])
? ? 代碼運行結(jié)果如下所示:
條件:[1,2] 輸入[1, 2] 結(jié)果:[1, 2]
條件:[1,(x,y)] 輸入[1, (2, 3)] 結(jié)果:x=2,y=3
條件:[x,*y,z] 輸入[1, {4, 5}] 結(jié)果:x=1,y=[],z={4, 5}
條件:[x,y,z] 輸入[1, 2, 3] 結(jié)果:x=1,y=2,z=3
條件:[x,*y,z] 輸入[1, 2, 3, 4, 5] 結(jié)果:x=1,y=[2, 3, 4],z=5
條件:[x,*y,z] 輸入[1, 100, 99, [2, 3, 4, 5], 9999] 結(jié)果:x=1,y=[100, 99, [2, 3, 4, 5]],z=9999
? ? 以上語句一些關(guān)鍵特性總結(jié)如下所示:
- 與解包賦值類似,元組和列表模式具有完全相同的含義并且在實際結(jié)果上都能匹配任意序列,區(qū)別是它們不能匹配迭代器或字符串
- 序列模式支持?jǐn)U展解包
[x,*y,z]和(x,*y,z)和相應(yīng)的解包賦值功能相同,而在 * 后面也可以接 _
? ? 以上的示例,可能比較復(fù)雜(平時應(yīng)該也不會這么為難自己),我們可以看看日常使得較多的示例
def match_list_sample(color:str):
match color.split():
case ["red","green","blue"]:
print(f"{color}")
case ["purple","yellow","red"]:
print(f"{color}")
case ["red"]:
print(f"{color}")
case _:
print("Not a color")
3.4 通配符模式
? ? 通配符模式更像是一種兜底模式,使用下劃線來匹配任何結(jié)果,但不綁定變量,示例如下所示:
def match_list_sample(color:str):
match color.split():
case ["red","green","blue"]:
print(f"{color}")
case _:
print("Not a color")
? ? 以上基礎(chǔ)模式,還可以有以下的擴(kuò)展模式
def match_list_sample(color:str):
match color.split():
case ["red","green","blue"]:
print(f"{color}")
case [_,_]:
print("Not a color")
通配模式中的
case _也是可以省略的,但在沒有任何匹配項,也是什么都不做
3.5 guard 模式
? ? match-case模式還支持在case后面添加if判斷,示例如下所示:
def guard_sample(number:List[int]):
match number:
case [x,y] if x>0 and y>0:
print("第一象限")
case [x,y] if x<0 and y>0:
print("第二象限")
case [x, y] if x < 0 and y < 0:
print("第三象限")
case [x, y] if x > 0 and y < 0:
print("第四象限")
case [_,_]:
print("數(shù)據(jù)錯誤")
3.6 字典模式
? ? 這種模式其實與前面的序列模式類似,只是將序列換成字典做匹配,示例代碼如下所示:
def dict_sample(config):
match config:
case {"env":env,"host":host}:
print(f"env:{env},host:{host}")
case {"env": env, **params}:
print(f"env:{env},params:{params}")
case _:
print("error")
if __name__ == "__main__":
dict_sample({"env":"Test","path":"/home/surpass","file":"surpass.xlsx"})
dict_sample({"env":"Test","host":"surpassme.net"})
? ? 運行結(jié)果如下所示:
env:Test,params:{'path': '/home/surpass', 'file': 'surpass.xlsx'}
env:Test,host:surpassme.net
匹配字典和序列不同之處在于,匹配序列時會要求序列的長度,而在匹配字典時僅看key,如果不確定序列長度,則推薦使用字典進(jìn)行匹配
3.7 AS模式
? ? 可以使用as來捕獲子模式,可以理解為多模式匹配的擴(kuò)展,示例代碼如下所示:
def as_sample(city:str):
match city:
case ("Shanghai" | "Jiangsu" | "Zhejiang" | "Anhui") as area:
print(f"我將去往華東-{area}")
case ("Hubei" | "Hunan" | "Henan" | "Jiangxi") as area:
print(f"我將去往華中-{area}")
case ("Guangdong" | "Guangxi" | "Hainan") as area:
print(f"我將去往華南-{area}")
case _:
print("error")
if __name__ == "__main__":
as_sample("Shanghai")
as_sample("Hainan")
? ? 運行結(jié)果如下所示:
我將去往華東-Shanghai
我將去往華南-Hainan
3.8 類模式
? ? case后面還支持類進(jìn)行判斷,示例代碼如下所示:
def class_match_sample(obj:Click):
match obj:
case Click(position=(0,0),button="Enter"):
print(f"position is (0,0),button is Enter")
case Click(position=(100, 200),button="Esc"):
print(f"position is (100, 200),button is Esc")
case _:
print("error")
if __name__ == "__main__":
class_match_sample(Click((0, 0), "Enter"))
class_match_sample(Click((100,200),"Esc"))
? ? 運行結(jié)果如下所示:
position is (0,0),button is Enter
position is (100, 200),button is Esc
? ? 但在使用類模式,位置需要確定,因此在case后的需要使用命名關(guān)鍵字參數(shù)傳參,不然會出現(xiàn)如下所示的報錯:
def class_match_error_sample(obj:Click):
match obj:
case Click((0,0),"Enter"):
print(f"position is (0,0),button is Enter")
case Click((100, 200),"Esc"):
print(f"position is (100, 200),button is Esc")
case _:
print("error")
? ? 如果使用以上的定義時,在運行時會出現(xiàn)以下所示的報錯信息:
Traceback (most recent call last):
File "C:\Users\Surpass\Documents\PyCharmProjects\TestNote\main.py", line 200, in <module>
class_match_error_sample(Click((0, 0), "Enter"))
File "C:\Users\Surpass\Documents\PyCharmProjects\TestNote\main.py", line 165, in class_match_error_sample
case Click((0,0),"Enter"):
^^^^^^^^^^^^^^^^^^^^
TypeError: Click() accepts 0 positional sub-patterns (2 given)
? ? 不過為了解決傳參的麻煩,官方提供了另一個方法,即在類里面添加__match_args__ 返回一個位置參數(shù)的元組,示例代碼如下所示:
class Click():
__match_args__ = ("position", "button")
def __init__(self, position:Tuple[int], button:str):
self.position = position
self.button = button
def class_match_sample(obj:Click):
match obj:
case Click((0,0),"Enter"):
print(f"position is (0,0),button is Enter")
case Click((100, 200),"Esc"):
print(f"position is (100, 200),button is Esc")
case _:
print("error")
4.注意事項
4.1 Python版本要求
- match語法僅在Python 3.10 及以上版本才支持,否則會引起語法錯誤
4.2 通配符使用
- _ 是一個特殊模式,表示匹配任何值
- 如果需要捕獲值,需要使用變量名
4.3 匹配的順序
- 匹配是按順序進(jìn)行的,一旦匹配成功,則后面的case將被忽略
- 在使用,建議將更具體的模式放在前面,通用的模式放在后面位置