Python 3.10 | Match Statement - 更靈活的 switch
概述
在我當初開始學 Python 時,發現沒有 switch 可用,令我感到有些驚訝,
還好從 Python 3.10 開始, Python 有了自己的 switch —— Structural Pattern Matching。
所以什麼是 Structural Pattern Matching ? 與 C++ 的 switch 相比,我覺得它更類似於 C# 的 Pattern Matching。
舉個簡單的例子:
1 | is_perform = False |
📌 Note
與 C++ 不同,Python 的match
沒有 fallthrough。
所以一個case
完成就會離開match
,而不會繼續執行下一個case
。
前言
文件中把 match
後的部分 ("anon", "soyorin"):
,稱為 subject_expr
,
為了方便理解,我們叫他 match value
。
Guards
case <pattern> if <expression>:
如果我們在 pattern 成功 match 後,想進一步檢查,可以在 pattern 後面加上 if
,
也就是範例中的 case ("anon", "soyorin") if is_perform:
,
這種用法稱為 Guard 。
執行流程大致為
- Pattern 成功 match,執行 Guard
- Pattern Mismatch,不執行 Guard
- Guard 結果為
True
→ 該case
執行 - Guard 結果為
False
→ 跳過該case
,繼續檢查下一個case
Irrefutable Case Blocks
指的是一定 match 的情況,類似於 C++ 的 default
,
但只能出現在 最後一個 case
,且整個 match 只能有 一個 這樣的 case
。
至於甚麼 pattern 符合?
可以參考 https://docs.python.org/3/reference/compound_stmts.html#irrefutable-case-blocks
舉例來說:
1 | match 2: |
Patterns
OR Patterns
就如同字面含意,就是個 or
,
pattern 會逐一嘗試直到其中一個成功為止。
舉個簡單例子:
1 | match 1: |
AS Patterns
前面 or
用的很開心,那我們如何取的原本的 value 呢?
此時我們可以用 as
來取得前面 match 到的值,
也就是case <pattern> as <name>:
,在 pattern 成功的情況下,
match value 會 bind 到 name 上,name = <match value>
。
接續前面的例子
1 | match 1: |
Literal Patterns
前面我們已經用了不少,用來比對 Python 中的 Literals,
如int
、string
、None
、bool
等等,
簡單來說,如果 if <match value> == <Literal>
就會比對成功,
若是遇到 Singletons ,如None
, True
,False
則會透過 is
來比對
Capture Patterns
用來將比對的值 bind 到變數上,
在 pattern 中,name 只能被 bind 一次
1 | match (1, 1): |
在下面例子中,在成功 match 的同時,"soyorin"
會 bind 到變數 y
上
1 | match ("anon", "soyorin"): |
Wildcard Patterns
_
,用來比對任意值,基本上就是當 default
來用。
比如
1 | match ("Raana", "soyorin"): |
Value Patterns
Value Pattern,是指可以透過 name resolution,
也就是透過 .
存取的變數,例如 enum
、math.pi
等。
藉由 ==
來比對,<match value> == <NAME1.NAME2>
。
例子
1 | from enum import Enum |
Group Patterns
老實說,我覺得這不算 pattern,就是告訴你可以加 ()
來加強可讀性
比如前面的例子
1 | match 1: |
Sequence Patterns
用來比對 “Sequence”,如 List
和 Tuple
等。
不過,str
、bytes
、bytearray
並不會被當作 Sequence Patterns。
📌 Note
- Python 不區分
(...)
和[...]
,兩者作用相同。- 另需注意的是
(3 | 4)
會是 group pattern,但[3 | 4]
依舊是 sequence pattern。
舉體的比對方式大致是
- 固定長度
- Match value 是 Sequence
len(value) == len(patterns)
- 從左到右依序比對
- 可變長度 (如
[first, *middle, last]
)- Match value 是 Sequence
- Sequence 長度小於扣除非
*
(star pattern) 的數量 → 比對失敗 - 比對前面非
*
(star pattern) 的部分(如同固定長度,也就是 first 部分) - 如果前面成功,扣除 last 部分後,收集剩餘元素(變成
list
,對應*middle
) - 最後比對剩餘部分,也就是 last (如同固定長度)
看幾個例子可能比較清楚
1 | # fixed-length |
1 | # variable-length |
Mapping Patterns
用來比對 “mapping”,最常用的就是 dict
與前面類似,我們可以把 **
(double_star_pattern) 放在最後,收集剩餘元素,
另外不可以有重複的 key
,否則會 SyntaxError
舉體的比對流程
- Match value 是 mapping
- pattern 的每個 key 有存在於 Match value
- key 對應的 value 與 pattern 相同
舉個例子
1 | match {"name": "Bob", "age": 30, "city": "NY"}: |
Class Patterns
用於比對 class
,但其比對流程相對較複雜。
和 function arguments 一樣,分為 positional arguments 和 keyword arguments 兩種形式。
比對流程
- Match value 是否是 builtin type
- 檢查 Match value 是不是 Patterns 的 instance ,透過
isinstance()
進行檢查。 - 檢查 Class Patterns 是否含有 arguments ,沒有就直接比對成功
如果有則分成 keyword 或 positional argument 兩種情形
- 只有 Keyword arguments:
- 檢查 attribute 是否存在於 Match value
- 檢查 attribute value 是否和 Pattern 相同
- 成功則往下個 keyword
- 如果有 Positional arguments:
- 藉由 Match value 的
__match_args__
attribute ,將 Positional arguments 轉換為 Keyword arguments
- 藉由 Match value 的
📌 Note
object.__match_args__
,若沒有定義,預設是一個 empty tuple()
- 部分 built-in types(如
bool
、int
、list
、str
等),是比對接收 positional argument 後的整個 object。
例子
1 | # Keyword argument |
1 | # positional argument |
結語
算是把 match statement 完整介紹了一遍,希望下篇別拖更 ( ̄︶ ̄)↗
如果有任何問題,歡迎在下面留言。
References
- https://docs.python.org/3/reference/compound_stmts.html#the-match-statement
- https://peps.python.org/pep-0636/
- https://peps.python.org/pep-0634/
- https://stackoverflow.com/questions/46701063/why-doesnt-python-have-switch-case-update-match-case-syntax-was-added-to-pyt
Photo by Mae Mu on Unsplash