Python | Structural Pattern Matching 火力加強版的 Switch


  1. 說明
  2. 小結
  3. 參考資料

介紹 Python 3.10 出現的新特色語法 Structural Pattern Matching,示範如何使用 Structural Pattern Matching 讓 Python 能夠寫得更為 Simple & Readability。

logo

說明

以往 Python 一直沒有 swtich,替代的方式是以連續使用 if 或使用 Dict 的 Key 作為 Switch 的 Case:

CLang

switch (score) {
  case score > 90:
    return 'A';
  case score > 80:
    return 'B';
  default:
    return 'C';
}

Python

if score > 90:
  return 'A'
elif score > 80:
  return 'B'
else:
  return 'C'

CLang

switch (monster) {
  case "slime":
    return "DQ";
  case "ratata":
    return "Pokemon";
  default:
    return "Unknown";
}

Python

{
  'slime': 'DQ',
  'ratata': 'Pokemon'
}.get(monster, 'Unknown')

而根據 Zen of python:

There should be one– and preferably only one –obvious way to do it.

不需要 switch 使用 if 以及 dict 同樣能夠優美的寫出 switch 的功能。但現在 switch 來了,且不只是 switch 而是 switch 火力加強版的 match (Structural Pattern Matching) 🤔

match 中的 if conditions 稱為 guard

match score:
    case x if x > 90:
        print('A')
    case x if x > 80:
        print('B')
    case _:
        print('C')
match monster:
    case 'slime':
        print('DQ')
    case 'ratata':
        print('Pokemon')
    case _:
        print('Unknown')

而 match (Structural Pattern Matching) 身為火力加強版的 switch 則是多了下列的功能:

Or Patterns

可以讓多個符合的情境有著相同的輸出行為。

match monster:
    case 'slime' | 'chimaera' | 'dracky':
        print('DQ')
    case 'ratata' | 'raticate':
        print('Pokemon')
    case _:
        print('Unknown')

As Patterns

match 可以比對值進行處理,並可以結合 Or Patterns 的使用:

match monster:
    case 'slime' | 'chimaera' | 'dracky' as monsterName:
        print(f'DQ {monsterName}')
    case 'ratata' | 'raticate' as pokeName:
        print(f'Pokemon {pokeName}')
    case _:
        print('Unknown')

此外 Structural Pattern Matching 真正的長處在於對多種資料型別進行比對:

listMatch.py

def listMatch(lst):
    match lst:
        case ['A']:
            return('Only A in the list')
        case ['A', x]:
            return(f'A and {x} in the list')
        case [_]:
            return('Only one element in list and it isn\'t A')
        case [a, b, *items, c, _]:
            return(f'{a} {b} {items} {c}')
print(listMatch(['A']))
# Only A in the list

print(listMatch(['A', 0]))
# A and 0 in the list

print(listMatch(['Z']))
# Only one element in list and it isn't A

print(listMatch([1, 2, 3, 4, 5]))
# 1 2 [3] 4

tupleMatch.py

使用 PEP636 的範例

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")

dictMatch.py

def appwiz(application):
    match application:
        case {'name': 'SQL Server', **rest}:
            print(f'SQL Server, Database Management System, related info: {rest}')
        case {'name': 'Chrome', **rest}:
            print(f'Chrome, Internet Browser, related info: {rest}')
        case {'name': _, **rest}:
            print(f'Unknown Application, info: {rest}')
appwiz({'name': 'SQL Server', 'edition': 'Developer'})
# SQL Server, Database Management System, related info: {'edition': 'Developer'}

appwiz({'name': 'Chrome', 'bit': '64'})
# Chrome, Internet Browser, related info: {'bit': '64'}

appwiz({'name': 'Visual Studio'})
# Unknown Application, info: {}

appwiz({})
#

小結

隨著 Python 加入了 match (Structural Pattern Matching),日後對於資料結構的比對值得複雜情境情境,會優先選擇使用 Structural Pattern Matching,以提升程式碼的可讀性以及維護的便利性。

參考資料

PEP636 | python.org