同步發表於個人 blog
Python 3.10 | Match Statement - 更靈活的 switch
在我當初開始學 Python 時,發現沒有 switch 可用,令我感到有些驚訝,
還好從 Python 3.10 開始, Python 有了自己的 switch —— Structural Pattern Matching。
所以什麼是 Structural Pattern Matching ? 與 C++ 的 switch 相比,我覺得它更類似於 C# 的 Pattern Matching。
舉個簡單的例子:
is_perform = False
match ("anon", "soyorin"):
# Mismatch
case 'tomorin':
print('組一輩子樂團')
# Mismatch: soyorin != rana
case ("anon", "rana"):
print('有趣的女人')
# Successful match, but guard fails
case ("anon", "soyorin") if is_perform:
print('為什麼要演奏春日影!')
# Matches and binds y to "soyorin"
case ("anon", y):
print(f'愛音,{y} 強到靠北')
# Pattern not attempted
case _:
print('我還是會繼續下去')
# 愛音,soyorin 強到靠北
📌 Note
與 C++ 不同,Python 的match
沒有 fallthrough。
所以一個case
完成就會離開match
,而不會繼續執行下一個case
。
文件中把 match
後的部分 ("anon", "soyorin"):
,稱為 subject_expr
,
為了方便理解,我們叫他 match value
。
case <pattern> if <expression>:
如果我們在 pattern 成功 match 後,想進一步檢查,可以在 pattern 後面加上 if
,
也就是範例中的 case ("anon", "soyorin") if is_perform:
,
這種用法稱為 Guard 。
執行流程大致為
True
→ 該 case
執行False
→ 跳過該 case
,繼續檢查下一個 case
指的是一定 match 的情況,類似於 C++ 的 default
,
但只能出現在 最後一個 case
,且整個 match 只能有 一個 這樣的 case
。
至於甚麼 pattern 符合?
可以參考 https://docs.python.org/3/reference/compound_stmts.html#irrefutable-case-blocks
舉例來說:
match 2:
case 1:
print("value is 1")
case x:
print("Irrefutable Case Blocks")
# Irrefutable Case Blocks
就如同字面含意,就是個 or
,
pattern 會逐一嘗試直到其中一個成功為止。
舉個簡單例子:
match 1:
case 1 | 2 | 3:
print("value is 1 or 2 or 3")
# value is 1 or 2 or 3
前面 or
用的很開心,那我們如何取的原本的 value 呢?
此時我們可以用 as
來取得前面 match 到的值,
也就是case <pattern> as <name>:
,在 pattern 成功的情況下,
match value 會 bind 到 name 上,name = <match value>
。
接續前面的例子
match 1:
case 1 | 2 | 3 as x:
print(f"value is {x}")
# value is 1
前面我們已經用了不少,用來比對 Python 中的 Literals,
如int
、string
、None
、bool
等等,
簡單來說,如果 if <match value> == <Literal>
就會比對成功,
若是遇到 Singletons ,如None
, True
,False
則會透過 is
來比對
用來將比對的值 bind 到變數上,
在 pattern 中,name 只能被 bind 一次
match (1, 1):
# SyntaxError
case x, x:
print(f"Matched: {x}")
# case x, x:
# ^
# SyntaxError: multiple assignments to name 'x' in pattern
在下面例子中,在成功 match 的同時,"soyorin"
會 bind 到變數 y
上
match ("anon", "soyorin"):
# Matches and binds y to "soyorin"
case ("anon", y):
print(f'愛音,{y} 強到靠北')
# 愛音,soyorin 強到靠北
_
,用來比對任意值,基本上就是當 default
來用。
比如
match ("Raana", "soyorin"):
# Matches and binds y to "soyorin"
case ("anon", y):
print(f'愛音,{y} 強到靠北')
# Pattern not attempted
case _:
print('我還是會繼續下去')
# 我還是會繼續下去
Value Pattern,是指可以透過 name resolution,
也就是透過 .
存取的變數,例如 enum
、math.pi
等。
藉由 ==
來比對,<match value> == <NAME1.NAME2>
。
例子
from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
match Color.RED:
case Color.RED:
print("RED")
case Color.GREEN:
print("GREEN")
case Color.BLUE:
print("BLUE")
case _:
print("unknown")
# RED
老實說,我覺得這不算 pattern,就是告訴你可以加 ()
來加強可讀性
比如前面的例子
match 1:
# case 1 | 2 | 3 as x:
case (1 | 2 | 3) as x:
print(f"value is {x}")
# value is 1
用來比對 "Sequence",如 List
和 Tuple
等。
不過,str
、bytes
、bytearray
並不會被當作 Sequence Patterns。
📌 Note
- Python 不區分
(...)
和[...]
,兩者作用相同。- 另需注意的是
(3 | 4)
會是 group pattern,但[3 | 4]
依舊是 sequence pattern。
舉體的比對方式大致是
len(value) == len(patterns)
[first, *middle, last]
)
*
(star pattern) 的數量 → 比對失敗*
(star pattern) 的部分(如同固定長度,也就是 first 部分)list
,對應 *middle
)看幾個例子可能比較清楚
# fixed-length
match [10, 20, 30]:
# note that this match can also bind names
case [x, y, z]:
print(f"x={x}, y={y}, z={z}")
# x=10, y=20, z=30
# variable-length
match [1, 2, 3, 4, 5]:
case [first, *middle, last]:
print(f"first={first}, middle={middle}, last={last}")
# first=1, middle=[2, 3, 4], last=5
用來比對 "mapping",最常用的就是 dict
與前面類似,我們可以把 **
(double_star_pattern) 放在最後,收集剩餘元素,
另外不可以有重複的 key
,否則會 SyntaxError
舉體的比對流程
舉個例子
match {"name": "Bob", "age": 30, "city": "NY"}:
case {"name": n, "age": a}:
print(f"name={n}, age={a}")
# name=Bob, age=30
用於比對 class
,但其比對流程相對較複雜。
和 function arguments 一樣,分為 positional arguments 和 keyword arguments 兩種形式。
比對流程
isinstance()
進行檢查。如果有則分成 keyword 或 positional argument 兩種情形
__match_args__
attribute ,將 Positional arguments 轉換為 Keyword arguments📌 Note
object.__match_args__
,若沒有定義,預設是一個 empty tuple()
- 部分 built-in types(如
bool
、int
、list
、str
等),是比對接收 positional argument 後的整個 object。
例子
# Keyword argument
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
match Point(1, 2):
case Point(x=1, y=y_value):
print(f"Matched! y={y_value}")
# Matched! y=2
# positional argument
class Point:
# assigned a tuple of strings
__match_args__ = ("x", "y")
def __init__(self, x, y):
self.x = x
self.y = y
match Point(1, 2):
# converted to keyword patterns using the __match_args__
case Point(1, y_value):
print(f"Matched! y={y_value}")
# Matched! y=2
算是把 match statement 完整介紹了一遍,希望下篇別拖更 ( ̄︶ ̄)↗
如果有任何問題,歡迎在下面留言。