Introduction to Structural Pattern Matching
Takanori Suzuki
PyCon APAC 2023 / 2023 Oct 27
構造的パターンマッチング 入門
Takanori Suzuki
PyCon APAC 2023 / 2023 Oct 27
Agenda / アジェンダ
Motivation and Goal / モチベーションとゴール
What’s New
Syntax / 構文
Patterns / パターン
Photos 📷 Tweets 🐦 👍
#pyconapac
/ #pyconapac_4
@takanory
Slide / スライド 💻
Who am I? / お前 誰よ 👤
Takanori Suzuki / 鈴木 たかのり ( @takanory)
PyCon JP Association: Chair
BeProud Inc.: Director / Python Climber
Python Boot Camp, Python mini Hack-a-thon, Python Bouldering Club
PyCon JP Association 🐍
PyCon JP / PyCon APAC: Annual event
Python Boot Camp: Tutorial for beginners
PyCon JP TV: YouTube Live
PyCon JP Association Meeting 2023 🍱
Oct 27, 12:30-13:30, 20F open space
Panelists: PyCon JP Association Board Members
Topics
Activities and financial report
Various discussions, so please join!
BeProud inc. 🏢
BeProud booth
We are hiring!!
Motivation of this talk 💪
発表のモチベーション
Structural Pattern Matching looks useful
You to know and use it
Goal of this talk 🥅
発表のゴール
Learn syntax and basic usage
Learn various patterns and how to use them
Try it tomorrow
Prerequisites
前提条件
Intermediate level
You should know Python syntax
tuple, list, dict, if, def, isinstance, dataclass, type hinting and more
Questions
質問
Are you using Python 3.10+? 🙋♂️
Python 3.10以上 を使っている人?
Do you know the new features? 🙋♀️
新機能 を知っている人?
What’s New in Python 3.10 🆕
Python 3.10の 新機能
What’s New in Python 3.10 🆕
Python Release Python 3.10.11 🐍
www.python.org/downloads/release/python-31011/
Who are You? / お前は 誰よ? 🐍
New features of Python 3.10
Parenthesized Context Managers
Better Typing Syntax
Better Error Messages
Structural Pattern Matching
Better Debugging
New features of Python 3.10
Parenthesized Context Managers
Better Typing Syntax
Better Error Messages
Structural Pattern Matching 👈
Better Debugging
Structural Pattern Matching
構造的パターンマッチング
Structural Pattern Matching
PEPs for Structural Pattern Matching
Motivation / モチベーション
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.)
Motivation / モチベーション
# check type or shape of an object
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
Syntax / 構文
Generic syntax of pattern matching
match subject:
case <pattern_1>:
<action_1>
case <pattern_2>:
<action_2>
case <pattern_3>:
<action_3>
case _:
<action_wildcard>
Soft keywords / ソフト キーワード
New in Python 3.10
match
,case
and_
Can be used identifier names
>>> match = "match" # Valid(Soft keyword)
>>> class = "class" # Invalid(Keyword)
File "<stdin>", line 1
class = "class"
^
SyntaxError: invalid syntax
Patterns / パターン
Patterns / パターン
match subject:
case <pattern_1>:
<action_1>
case <pattern_2>:
<action_2>
case <pattern_3>:
<action_3>
case _:
<action_wildcard>
Literal patterns
リテラル パターン
match beer_style: # "Pilsner" / "IPA" / "Ale"
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 patterns
OR パターン
|
is OR
match beer_style: # "IPA" / "Session IPA"
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"
Literal patterns without wildcard
ワイルドカードなし のリテラルパターン
match beer_style: # "Ale"
case "Pilsner":
result = "First drink"
case "IPA":
result = "I like it"
case "Hazy IPA":
result = "Cloudy and cloudy"
# case _:
# result = "I like most beers"
Useful? 🤔
便利そう?そうでもない?
rewrite with if statement
if 文で書き直す
If written as an
if
statement
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"
Looks similer 👯
よく 似ている
Pattern Matching is Powerful 💪
パターンマッチングは 強力
Literal and Variable patterns
リテラルと 変数 パターン
Literal and Variable patterns
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."
Literal and Variable patterns
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."
order = ("", "")
print(order_beer_and_food(order)) # -> Please order something.
Literal and Variable patterns
"IPA"
assign tobeer
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."
order = ("IPA", "")
print(order_beer_and_food(order)) # -> I drink IPA.
Literal and Variable patterns
"IPA"
assign tobeer
、"nuts"
assign tofood
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."
order = ("IPA", "nuts")
print(order_beer_and_food(order)) # -> I drink IPA with nuts.
Literal and Variable patterns
Tuple length does not match
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."
order = ("IPA", "nuts", "spam")
print(order_beer_and_food(order)) # -> one beer and one food only.
rewrite with if statement
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."
Which code do you prefer?
どっちのコードが 好み ?
Pattern Matching 🆚
if
statement
Case Order is important ⬇️
ケースの順番 は重要
Case Order is important ⬇️
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 = ("", "nuts")
print(order_beer_and_food(order)) # -> I drink with nuts.
Classes patterns
クラス パターン
Classes patterns
@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."
Results: Classes patterns
実行結果: クラスパターン
>>> 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.'
Classes patterns
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."
rewrite with if statement
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."
Use multiple classses
複数 のクラスを使用
@dataclass
class Beer: # Beer("IPA", "Pint")
style: str
size: str
@dataclass
class Food: # Food("nuts")
name: str
@dataclass
class Water: # Water(4)
number: int
Use multiple classses
複数 のクラスを使用
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."
rewrite with if statement
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."
Sequense patterns ➡️
シーケンス パターン
Sequense patterns ➡️
Parse the order text
for example:
order_text = "beer IPA pint"
order_text = "food nuts"
order_text = "water 3"
order_text = "bill"
Matching by length of sequence
シーケンスの 長さ でマッチ
match order_text.split():
case [action]: # match "bill"
...
case [action, name]: # match "food nuts", "water 3"
...
case [action, name, size]: # match "beer IPA pint"
...
Matching specific values in sequence
シーケンス中の 任意の値 でマッチ
Specific values:
"bill"
,"food"
…
match order_text.split():
case ["bill"]: # match "bill"
calculate_amount()
case ["food", food]: # match "food nuts"
tell_kitchen(food)
case ["water", number]: # match "water 3"
grass_of_water(number)
case ["beer", style, size]: # match "beer IPA pint"
tell_beer_master(style, size)
Capturing matched sub-patterns
マッチした サブパターン を捕捉する
Valid beer size:
"Pint"
or"HalfPint"
"beer IPA Small"
is invalid
order_text = "beer IPA Pint"
match order_text.split():
...
case ["beer", style, ("Pint" | "HalfPint")]:
# I don't know beer size
Capturing matched sub-patterns
マッチした サブパターン を捕捉する
Use as patterns
("Pint" | "HalfPint") as size
order_text = "beer IPA Pint"
match order_text.split():
...
case ["beer", style, ("Pint" | "HalfPint") as size]:
tell_beer_master(style, size) # size is "Pint"
Matching multiple values
複数の値 にマッチ
I want to order several foods
example:
"food nuts fries pizza"
order_text = "food nuts fries pizza"
match order_text.split():
...
case ["food", food]: # capture single value
tell_kitchen(food)
Matching multiple values
複数の値 にマッチ
food
→*foods
order_text = "food nuts fries pizza"
match order_text.split():
...
case ["food", *foods]: # capture multiple values
for food in foods: # ("nuts", "fries", "pizza")
tell_kitchen(food)
I can order several foods!! 🍟🍕
複数の食べ物を注文できる!!
Mapping Patterns 📕
マッピング パターン
Mapping Patterns 📕
Pattern match for dict
Useful for alalyzing JSON
order_dict = {"beer": "IPA", "size": "Pint"}
match order_dict:
case {"food": food}:
tell_kitchen(food)
case {"beer": style, "size": ("Pint" | "HalfPint") as size}:
tell_beer_master(style, size)
case {"beer": style, "size": _}:
print("Unknown beer size")
case {"water": number}:
grass_of_water(number)
case {"bill": _}:
calculate_amount()
Matching builtin classes
組み込み クラスにマッチ
Use str(), int() and more
order_dict = {"water": 3}
# order_dict = {"water": "three"} # Doesn't match
match order_dict:
case {"food": str(food)}:
tell_kitchen(food)
case {"beer": str(style), "size": ("Pint" | "HalfPint") as size}:
tell_beer_master(style, size)
case {"beer": style, "size": _}:
print("Unknown beer size")
case {"water": int(number)}:
grass_of_water(number)
case {"bill": _}:
calculate_amount()
Guards 💂♀️
ガード
Number of water
水の 数
Invalid: 9, 10, …
Valid: 1, 2, … 8
Invalid: 0, -1, …
Guards 💂♀
Valid: 1, 2, … 8
if
statement after pattern
order_list = ["water", 3] # -> 3 glasses of water, please.
# order_list = ["water", 15] # -> 15 is invalid value.
match order_list:
case ["water", int(number)] if 1 <= number <= 8:
print(f"{number} glasses of water, please.")
case ["water", _]:
print(f"{number} is invalid value.")
Can’t order many glasses of water! 🎉
たくさんの水 を注文できない!
Summary / まとめ
Summary / まとめ
Motivation and Goal / モチベーションとゴール
What’s New
Syntax / 構文
Soft keywords:
match
,case
and_
Patterns / パターン
Literal, Variable, Classes, Sequense, Mapping
Wildcard(
_
), OR(|
), AS, Guards
Try Structural Pattern Matching 👍
構造的パターンマッチングを 試そう
References 📚
Thank you !! 🙏
@takanory takanory takanory takanory
Finally, 2 important notices
最後に、2つの 重要な お知らせ
1: PyCon APAC 2023 Original Beer
PyCon APAC 2023 オリジナルビール
Only available at Official party
オフィシャルパーティー でのみ飲めます
I prepared beer in → Video
If you can drink beer, you should join the party!!
2: Unofficial Sprint after-party
非公式 Sprint 打ち上げ
Thank you !! 🙏
@takanory takanory takanory takanory