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

2021-08-16

介紹 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