Python基礎(chǔ)-27 match-case 使用教程

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將被忽略
  • 在使用,建議將更具體的模式放在前面,通用的模式放在后面位置

5.參考資料

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容