# Python 的 Pattern Matching

> Python 的模式比對功能在 3.10 版本中引入，提供了結構化模式比對的語法。這項新特性取代了繁瑣的多重條件判斷，讓程式碼更具可讀性。透過 match 語法，開發者能夠輕鬆處理複雜的資料結構，並進行型別及值的比對，提升編碼效率，是 Python 開發中不可或缺的工具。

Published: 2024-02-27
URL: https://kaochenlong.com/pattern-matching-in-python

---

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


