Python 的 Pattern Matching
假設你曾經寫過其它的程式語言,遇到這種一大串的 else if
組合的時候,通常會想到的是 switch
或 case...when
之類的語法。在 Python 並沒有這樣的設計,真的遇到一堆判斷就是用 if...elif...else...
寫就是了。這樣的設計是因為 Python 的作者認為 switch
語法並不是那麼的必要,而且 if...elif...else...
也可以達到相同的效果,所以就沒有加入 switch
語法。沒有 switch
或 case
的設計並沒什麼大不了的,事實上有些程式語言就算有 switch
或 case
的寫法,本質上也只是 if..else
而已。
match 比對
其實 switch
語法一堆人在敲碗,但碗都敲破了也沒有用,提議一直被否決,就覺得不太需要。不過在許多人的努力,在 PEP 上討論了好陣子,終於在 PEP 634 定案,並且 Python 3.10 實作了新的語法 match
,寫起來的手感跟其它程式語言的 switch
語法有點像。match
語法的正式名稱叫做「結構化模式比對(Structural Pattern Matching)」,由於是在 3.10 版之後才有,所以太舊版本的 Python 是沒有支援這種寫法。假設我有原本個簡單流程判斷像這樣:
weather = "rain"
if weather == "rain":
print("下雨天")
elif weather == "sunny":
print("出太陽")
elif weather == "cloudy":
print("多雲")
else:
print("不知道")
使用 match
語法的話,可以寫成這樣:
weather = "rain"
match weather:
case "rain":
print("下雨天")
case "sunny":
print("出太陽")
case "cloudy":
print("多雲")
case something:
print(f"我不知道 {something} 是什麼天氣")
前面三個 case
分別是在比對字串(Python 會使用 ==
來進行比對),如果比對成功的話就會結束整個比對的過程,而最後一個 case
是指如果上面所有的路線都比對失敗的話會走的路,差不多就是 else
的概念。在後面加上變數名稱的話表示比對到的值會被指派給這個變數。至於要用什麼名字倒是無所謂,但如果發現在這個 case
裡根本沒打算使用它的話,可以給它一個底線 _
:
weather = "rain"
match weather:
case "rain":
print("下雨天")
case "sunny":
print("出太陽")
case "cloudy":
print("多雲")
case _:
print("我根本不在乎是什麼天氣")
那個 _
代表「I don't care」的意思。看到這裡,大家有覺得這樣語法看起來有比較清楚嗎?老實說我覺得並沒有差多少。
如果只是這樣的簡單比對的話,我也認同用 if..else..
還比較直覺一點。但如果你以為 match
語法就只有這樣,那你就太小看 Pattern Matching 了,Pattern Matching 在其它程式語言可是大家搶著抄但又不一定抄的起來的好用設計。再舉個例子,假設我想判斷某個值是數字還是字串,用 if..else..
寫起來大概像這樣:
value = True
if type(value) in [int, float]:
print("數字")
elif type(value) == str:
print("字串")
else:
print("我不知道你是誰")
match
不只能比對簡單的值,還能比對型別,所以改用 match
寫的話:
data = 123
match data:
case int() | float():
print("整數")
case str():
print("字串")
case _:
print("其他型別")
在第一個 case
中間的那個 |
表示是「或者」的意思,雖然整個行數沒有變比較少,但程式碼的可讀性有比較好一點點。match
還能用來比對串列、字典、集合等等資料結構,在處理比較複雜的資料結構時挺方便的:
user_data = [1, "悟空", 18]
match user_data:
case [id, name, age]:
print(f"{id} - 我是 {name},我今年 {age} 歲")
case _:
print("你好")
在 user_data
裡面有三個值,透過 Pattern Matching 如果有「比對」到一樣的模式,被比對到的部份可以直接變成變數。也許你並不需要所有的變數,如果你只是想要得到資料裡的第一個值,剩下的不需要,可以用底線 _
來代表不需要的部份:
user_data = [1, "悟空", 18]
match user_data:
case [id, _, _]:
print(f"會員編號:{id}")
case _:
print("hi")
這應該就不是一般的 if..else..
或其它程式語言的 switch
能夠輕鬆做到的事了。
除了串列、Tuple 能比對,字典也能比對:
user_data = {"name": "Kitty", "gender": "female"}
match user_data:
case {"gender": "male", "name": name}:
print(f"{name} 先生您好!")
case {"gender": "female", "name": name}:
print(f"{name} 女士您好!")
case _:
print("您好!")
原本一般大概會使用 if..else..
來判斷 gender
的值再決定怎麼打招呼,但透過 Pattern Matching 可以直接比對到字典裡 gender
的值,而且順序還不需要一樣,這樣的寫法看起來更直覺。Python 的 match
不只這樣,還能在 case
後面加上條件,像這樣:
number = [10, 0]
match number:
case x, y if y != 0:
print(x / y)
case _:
print("第二個數字不能為零!")
另外,match
不只一次比對一個,還能使用 *
符號一次比對多個,像這樣:
numbers = [1, 2, 3, 4, 5]
match numbers:
case _, *others:
print(others)
case _:
print("Hello Python")
第一個元素我刻意使用 _
表示我不需要,但後面的元素可以使用 *
符號把它全部收下來,這個應該也是其它程式語言的 switch
辦不到的事。講了這麼多關於 match
的介紹,最後用一個簡單但算經驗的例子來做個收尾,大家不知道有沒有聽過「費氏數列(Fibonacci)」?這個數列的規則是:
- 第 1 項是 0
- 第 2 項是 1
- 第 3 項開始每一項都是前兩項的和,所以這個數列 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ... 以此類推。
這東西跟我們的年夜飯的概念有點像,大年初三吃的就是大年初一加上初二的總合。這個數列是很經典的「遞迴函數」的實作,用 match
可以寫的很簡潔易懂:
def fib(n):
match n:
case n if n < 2:
return n
case _:
return fib(n - 1) + fib(n - 2)
print(fib(6)) # 第 6 項 = 8
print(fib(8)) # 第 8 項 = 21
Python 的 不像其它程式語言的 switch
只是 if..else..
的變形或替代品,它還能用來比對或拆解更複雜的資料結構,在適當的地方使用 match
可以讓程式碼看起來更直覺、更易懂。
工商:想學 Python 嗎?我教你啊!
想要成為軟體工程師嗎?這不是條輕鬆的路,需要有足夠的決心、設定目標並持續學習,我們的 ASTROCamp 軟體工程師培訓營提供專業的前後端課程培訓,幫助你在最短時間內建立正確且扎實的軟體開發技能,有興趣而且不怕吃苦的話不妨來試試看吧 :)