Introduction to Structural Pattern Matching
Takanori Suzuki
Start Python Club 79 / 2022 Mar 10
Agenda / アジェンダ
Motivation / モチベーション
What’s New / 更新情報
Syntax / 構文
Patterns / パターン
Photos 📷 Tweets 🐦 👍
#stapy
/ @takanory
Slide 💻
Who am I? / お前誰よ 👤
Takanori Suzuki / 鈴木 たかのり ( @takanory)
PyCon JP Association: 副代表理事
BeProud: 取締役 / Python Climber
Python Boot Camp, Python mini Hack-a-thon, Python Bouldering Club
Books ( 著者ページ) 📚
プレゼント!! 🎁
PyCon JP 🐍
一般社団法人PyCon JP Association
PSF Community Service Awardを受賞 🎉
We are pleased to inform you of the PSF’s decision to award the Q4 2021 Community Service Award to you for your many years of community work.
PyCon JP 2022 🎫
秋の開催に向けて活動開始
スタッフ募集中
ブログ: pyconjp.blogspot.com
Python Boot Camp 💪
初心者向けPythonチュートリアル
BeProud Inc. 🏢
Pythonでシステム開発、研修、自社サービス
この発表の モチベーション 💪
Structural Pattern Matching は 便利そう
みんなに 知って、使って みてほしい
この発表の ゴール 🥅
構文 と 基本的な使い方 を知る
さまざまな パターン と、その 使い方 を知る
明日から 試せる
前提条件
中級 レベル
Pythonの文法 を理解している
タプル、リスト、辞書、if、def、isinstance、データクラス、型ヒントなど
質問
Python 3.10を使ってますか? 🙋♂️
3.10の新機能を知ってますか? 🙋♀️
What’s New in Python 3.10 🆕
What’s New in Python 3.10 🆕
Python Release Python 3.10.0
www.python.org/downloads/release/python-3100/
お前誰よ? 🐍
Python 3.10の 新機能
Parenthesized Context Managers
Better Typing Syntax
Better Error Messages
Structural Pattern Matching
Better Debugging
Python 3.10の 新機能
Parenthesized Context Managers
Better Typing Syntax
Better Error Messages
Structural Pattern Matching 👈
Better Debugging
Structural Pattern Matching 🏛
Structural Pattern Matching 🏛
Structural Pattern Matchingの PEP
モチベーション
www.python.org/dev/peps/pep-0635/#motivation
(Structural) pattern matching syntax is found in many languages, from Haskell, Erlang and Scala to Elixir and Ruby. (A proposal for JavaScript is also under consideration.)
モチベーション
www.python.org/dev/peps/pep-0635/#motivation
(構造的)パターンマッチの構文は、Haskell、Erlang、ScalaからElixir、Rubyなど、多くの言語で見られます(JavaScriptへの提案も検討中)。
モチベーション
# オブジェクトの型や形を確認する
if isinstance(x, tuple) and len(x) == 2:
host, port = x
mode = "http"
elif isinstance(x, tuple) and len(x) == 3:
host, port, mode = x
# Structural Pattern Matching
match x:
case host, port:
mode = "http"
case host, port, mode:
pass
構文
Pattern Matchingの基本的な構文
match subject:
case <pattern_1>:
<action_1>
case <pattern_2>:
<action_2>
case <pattern_3>:
<action_3>
case _:
<action_wildcard>
ソフト キーワード
Python 3.10の 新仕様
match
、case
、_
識別子 に使用可能
>>> match = 'match' # OK
>>> class = 'class' # NG
File "<stdin>", line 1
class = 'class' # NG
^
SyntaxError: invalid syntax
パターン
パターン
match subject:
case <pattern_1>:
<action_1>
case <pattern_2>:
<action_2>
case <pattern_3>:
<action_3>
case _:
<action_wildcard>
リテラル パターン
match beer_style:
case "Pilsner":
result = "First drink"
case "IPA":
result = "I like it"
case "Hazy IPA":
result = "Cloudy and cloudy"
case _:
result = "I like most beers"
OR パターン
|
は OR
match beer_style:
case "Pilsner":
result = "First drink"
case "IPA" | "Session IPA":
result = "I like it"
case "Hazy IPA":
result = "Cloudy and cloudy"
case _:
result = "I like most beers"
wildcardなし のLiteralパターン
match beer_style:
case "Pilsner":
result = "First drink"
case "IPA":
result = "I like it"
case "Hazy IPA":
result = "Cloudy and cloudy"
# case _:
# result = "I like most beers"
? 🤔
if 文で書き換える
if
文で書いた場合
if beer_style == "Pilsner":
result = "First drink"
elif beer_style == "IPA" or beer_style == "Session IPA":
result = "I like it"
elif beer_style == "Hazy IPA":
result = "Cloudy and cloudy"
else:
result = "I like most beers"
Pattern Matchingは パワフル 💪
リテラルと 変数 パターン
リテラルと 変数 パターン
長さ2のタプルが注文を表す
order1 = ("IPA", "nuts") # ビールとフード
order2 = ("Pilsner", "") # ビールのみ
order3 = ("", "fries") # フードのみ
order4 = ("", "") # なにも注文しない
order_beer_and_food(order1) # -> I dring IPA with nuts.
リテラルと 変数 パターン
def order_beer_and_food(order: tuple) -> str:
match order:
case ("", ""):
return "Please order something."
case (beer, ""):
return f"I drink {beer}."
case ("", food):
return f"I eat {food}."
case (beer, food):
return f"I drink {beer} with {food}."
case _:
return "one beer and one food only."
リテラルと 変数 パターン
def order_beer_and_food(order: tuple) -> str:
match order:
case ("", ""): # match here
return "Please order something."
case (beer, ""):
return f"I drink {beer}."
case ("", food):
return f"I eat {food}."
case (beer, food):
return f"I drink {beer} with {food}."
case _:
return "one beer and one food only."
order_beer_and_food(("", "")) # -> Please order something.
リテラルと 変数 パターン
"IPA"
がbeer
に代入
def order_beer_and_food(order: tuple) -> str:
match order:
case ("", ""):
return "Please order something."
case (beer, ""): # match here
return f"I drink {beer}."
case ("", food):
return f"I eat {food}."
case (beer, food):
return f"I drink {beer} with {food}."
case _:
return "one beer and one food only."
order_beer_and_food(("IPA", "")) # -> I drink IPA.
リテラルと 変数 パターン
"IPA"
がbeer
に代入"nuts"
がfood
に代入
def order_beer_and_food(order: tuple) -> str:
match order:
case ("", ""):
return "Please order something."
case (beer, ""):
return f"I drink {beer}."
case ("", food):
return f"I eat {food}."
case (beer, food): # match here
return f"I drink {beer} with {food}."
case _:
return "one beer and one food only."
order_beer_and_food(("IPA", "nuts")) # -> I drink IPA with nuts.
リテラルと 変数 パターン
タプルの長さが一致しない
def order_beer_and_food(order: tuple) -> str:
match order:
case ("", ""):
return "Please order something."
case (beer, ""):
return f"I drink {beer}."
case ("", food):
return f"I eat {food}."
case (beer, food):
return f"I drink {beer} with {food}."
case _: # match here
return "one beer and one food only."
order_beer_and_food(("IPA", "nuts", "spam")) # -> one beer and one food only.
if 文で書き換える
def order_beer_and_food(order: tuple) -> str:
if len(order) == 2:
beer, food = order
if beer == "" and food == "":
return "I'm full."
elif beer != "" and food == "":
return f"I drink {beer}."
elif beer == "" and food != "":
return f"I eat {food}."
else:
return f"I drink {beer} with {food}."
else:
return "one beer and one food only."
どっちが好み?
Structural Pattern Matching
if
文
順番 は重要 ⬇️
def order_beer_and_food(order: tuple) -> str:
match order:
case (beer, food): # match here
return f"I drink {beer} with {food}."
case ("", ""): # never reach
return "Please order something."
case (beer, ""): # never reach
return f"I drink {beer}."
case ("", food): # never reach
return f"I eat {food}."
case _:
return "one beer and one food only."
order_beer_and_food(("IPA", "")) # -> I drink IPA with .
クラス パターン
クラス パターン
@dataclass
class Order: # Order(beer="IPA"), Order("Ale", "nuts")...
beer: str = ""
food: str = ""
def order_with_class(order: Order) -> str:
match order:
case Order(beer="", food=""):
return "Please order something."
case Order(beer=beer, food=""):
return f"I drink {beer}."
case Order(beer="", food=food):
return f"I eat {food}."
case Order(beer=beer, food=food):
return f"I drink {beer} with {food}."
case _:
return "Not an order."
クラスパターンの 実行結果
>>> order_with_class(Order())
'Please order something.'
>>> order_with_class(Order(beer="Ale"))
'I drink Ale.'
>>> order_with_class(Order(food="fries"))
'I eat fries.'
>>> order_with_class(Order("Ale", "fries"))
'I drink Ale with fries.'
>>> order_with_class("IPA")
'Not an order.'
クラスパターン
def order_with_class(order: Order) -> str:
match order:
case Order(beer="", food=""):
return "Please order something."
case Order(beer=beer, food=""):
return f"I drink {beer}."
case Order(beer="", food=food):
return f"I eat {food}."
case Order(beer=beer, food=food):
return f"I drink {beer} with {food}."
case _:
return "Not an order."
if 文で書き換える
def order_with_class(order: Order) -> str:
if isinstance(order, Order):
if order.beer == "" and order.food == "":
return "Please order something."
elif order.beer != "" and order.food == "":
return f"I drink {order.beer}."
elif order.beer == "" and order.food != "":
return f"I eat {order.food}."
else:
return f"I drink {order.beer} with {order.food}."
else:
return "Not an order."
注文用 クラス
@dataclass
class Beer: # Beer("IPA", "Pint")
style: str
size: str
@dataclass
class Food: # Food("nuts")
name: str
@dataclass
class Water: # Water(4)
number: int
複数のクラス を使うパターン
def order_with_classes(order: Beer|Food|Water) -> str:
match order:
case Beer(style=style, size=size):
return f"I drink {size} of {style}."
case Food(name=name):
return f"I eat {name}."
case Water(number=number):
return f"{number} glasses of water, please."
case _:
return "Not an order."
if 文で書き換える
def order_with_classes(order: Beer|Food|Water) -> str:
if isinstance(order, Beer):
return f"I drink {order.size} of {order.style}."
elif isinstance(order, Food):
return f"I eat {order.name}."
elif isinstance(order, Water):
return f"{order.number} glasses of water, please."
else:
return "Not an order."
シーケンス パターン ➡️
注文テキストを解析
リストに変換してパターンマッチ
order_text = "beer IPA pint"
order_text.split() # -> ["beer", "IPA", "pint"]
order_text = "food nuts"
order_text = "water 3"
order_text = "bill"
シーケンスの 長さ でマッチ
match order_text.split():
case [action]:
# ["bill"] にマッチ
...
case [action, name]:
# ["food", "nuts"]、["water", "3"] にマッチ
...
# 処理を分岐したい
case [action, name, size]:
# ["beer", "IPA", "pint"] にマッチ
...
特定の値 にマッチ
特定の値(bill, food…)にマッチ
シーケンス + リテラル パターン
match order_text.split():
case ["bill"]: # ["bill"] にのみマッチ
calculate_amount()
case ["food", food]: # ["food", "nuts"]
tell_kitchen(food)
case ["water", number]: # ["water", "3"]
glass_of_water(number)
case ["beer", style, size]: # ["beer", "IPA", "pint"]
tell_beer_master(style, size)
任意の値 にマッチ
有効なビールサイズ:
pint
、half
"beer IPA 1-liter"
はマッチしない
match order_text.split():
...
case ["beer", style, ("pint" | "half")]: # ORパターン
# tell_beer_master(style, size)
# ビールのサイズはどっち?
AS パターン
サブパターン の値を取得
サイズ(
pint
またはhalf
)をsize
に代入
match order_text.split():
...
case ["beer", style, ("pint" | "half") as size]:
tell_beer_master(style, size)
任意の長さの値 にマッチ
複数の料理の注文に対応する
例:
"food nuts fries pickles"
order_text = "food nuts fries pickles"
match order_text.split():
...
case ["food", food]: # マッチしない
tell_kitchen(food)
任意の長さの値 にマッチ
変数名に アスタリスク (
*
)を追加
order_text = "food nuts fries pickles"
match order_text.split():
...
case ["food", *foods]: # 任意の長さの値をキャプチャ
for food in foods: # ("nuts", "fries", "pickles")
tell_kitchen(name)
マッピング パターン 📕
マッピング パターン 📕
辞書 用のパターン
JSON の解析に便利
order_dict = {"beer": "IPA", "size": "pint"}
match order_dict:
case {"food": food}:
tell_kitchen(food)
case {"beer": style, "size": ("pint" | "half") as size}:
tell_beer_master(style, size)
case {"beer": style, "size": _}:
print("Unknown beer size")
case {"water": number}:
glass_of_water(number)
case {"bill": _}:
calculate_amount()
組み込み クラスにマッチ
料理名は文字列、水の数は整数
str()
、int()
などを使う
order_dict = {"water": 3}
# order_dict = {"water": "three"} # マッチしない
match order_dict:
case {"food": str(food)}:
tell_kitchen(food)
...
case {"water": int(number)}:
glass_of_water(number)
...
ガード 💂♀️
ガード 💂♀️
パターンの後ろに if 文
水は1〜9杯しか頼めない
order_dict = {"water": 3} # 有効な値
# order_dict = {"water": 15} # -> 水は1〜9杯です
# order_dict = {"water": "three"} # -> 水は数値で指定してください
match order_dict:
case {"water": int(number)} if 0 < number < 10:
glass_of_water(number)
case {"water": int(number)}:
print("水は1〜9杯です")
case {"water": _}:
print("水は数値で指定してください")
まとめ
まとめ
モチベーション 💪
構文
ソフトキーワード:
match
、case
、_
パターン
リテラル、ワイルドカード、変数、クラス、シーケンス、マッピング、OR、AS、ガード
Structural Pattern Matching に 挑戦 👍
参考資料 📚
Thank you !! 🙏
Takanori Suzuki ( @takanory)