鈴木たかのりです。今月からgihyo.
第1回目はPython 3.
Python 3.10の新機能
Python 3.
Python 3.
この画像はPython 3.
- Parenthesized Context Managers
- Better Typing Syntax
- Better Error Messages
- Structural Pattern Matching
- Better Debugging
今回はそのうち
構造化パターンマッチングとは
構造化パターンマッチングはPythonの新しい文法です。match
文とcase
文を使用して以下のようなコードが書けるようになります。beer_
変数の値によって処理が分岐し、result
変数に任意の文字列が入ります。"Pilsner"
、"IPA"
などがパターンです。
どのパターンにもマッチしない場合は_
にマッチします。この_
をワイルドカードパターンと呼びます。
match beer_style: # Pilsner, IPA, Hazy IPA and others
case "Pilsner":
result = "First drink"
case "IPA":
result = "I like it"
case "Hazy IPA":
result = "Cloudy and cloudy"
case _: # Wildcard
result = "I like most beers"
構造化パターンマッチングのPEP
Pythonで新機能を追加するにはPEP
構造化パターンマッチングはとても大きな機能のため、3つのPEPが存在します
- PEP 634 – Structural Pattern Matching: Specification
- PEP 635 – Structural Pattern Matching: Motivation and Rationale
- PEP 636 – Structural Pattern Matching: Tutorial
この記事では一部機能しか紹介できないので、詳細な仕様やチュートリアルを確認したい方は、ぜひPEPドキュメントを読んでみてください。
構造化パターンマッチングの文法
文法は以下のようになります。subject
の内容がcase
の後ろに書いてあるいずれかのパターンにマッチすると、そのパターンのブロックにあるアクションが実行されます。
なお、パターンはどれか1つにマッチしたら、アクションの実行後にmatch
のブロックを抜けます。全てのパターンにマッチせず最後にワイルドカード_
)
match subject:
case <pattern_1>:
<action_1>
case <pattern_2>:
<action_2>
case <pattern_3>:
<action_3>
case _:
<action_wildcard>
パターンマッチングで使用するmatch
、case
、_
はソフトキーワードというPython文法上の新しい概念です。if
、for
などのキーワードと異なり、変数名等に使用可能です。
>>> if = "if"
File "<stdin>", line 1
if = "if"
^
SyntaxError: invalid syntax
>>> match = "match"
※参考:2.
構造化パターンマッチングのなにが便利なのか
冒頭のコード例を見て
キャプチャーパターン
キャプチャーパターンではパターンにマッチしたときに変数に値が代入されます。コード例を見てみましょう。
order = ("beer", "IPA") # ビールの注文
# order = ("food", "Pizza") # フードの注文
match order:
case ("beer", beer):
print(f"ビール「{beer}」をお持ちしました")
case ("food", food):
print(f"フード「{food}」を召し上がれ")
このコードを実行するとビール
」"beer"
にマッチし、2つ目の要素がbeer
変数に代入されています。
このコードをif文で書き換えると以下のようになります。まずorder
がタプルやリストなどのSequence
型か、そして要素数が2つかを確認します。その後1つ目の要素を確認し、2つ目の要素を取り出しています。
どうでしょうか? 構造化パターンマッチングの方が読みやすい気がしませんか?
from collections.abc import Sequence
if isinstance(order, Sequence) and len(order) == 2:
if order[0] == "beer":
beer = order[1]
print(f"ビール「{beer}」をお持ちしました")
elif order[0] == "food":
food = order[1]
print(f"フード「{food}」を召し上がれ")
他にもさまざまなパターンがあります。
クラスパターン
オブジェクトの型でパターンマッチができます。データクラスと組み合わせて使用すると、コードが読みやすくなります。
※参考:dataclasses --- データクラス
以下のような注文用の3種類のデータクラスを用意します。
from dataclasses import dataclass
@dataclass
class Beer: # Beer("IPA", "Pint")
style: str
size: str
@dataclass
class Food: # Food("nuts")
name: str
@dataclass
class Water: # Water(4)
number: int
そして、クラスパターンでクラスの種類ごとに分岐し、その値をキャプチャーパターンで取り出しています。
order = Beer("IPA", "Pint")
match (order):
case Beer(style=style, size=size):
print(f"{style}を{size}サイズでください")
case Food(name=name):
print(f"{name}を食べたいです")
case Water(number=number):
print(f"水を{number}人分ください")
case _:
print("これは注文ではありません")
if文で書き換えると以下のようになります。構造化パターンマッチングの方がシンプルに書けて、意図を読み取りやすいと思います。
if isinstance(order, Beer):
style = order.style
size = order.size
print(f"{style}を{size}サイズでください")
elif isinstance(order, Food):
(略)
else:
print("これは注文ではありません")
その他のパターン
他のパターンを簡単に紹介します。
- ORパターン:
|
で複数のパターンのいずれかにマッチ - ASパターン: サブパターンでマッチした値を
as
で変数に代入 - マッピングパターン: 辞書などにマッチ
order = "IPA"
# order = ("IPA", "Half")
# order = ("Pilsner", "MASS")
# order = {"beer": "Hazy IPA", "size": "Pint"}
match order:
case "IPA" | "Hazy IPA": # ORパターン
print("好きなスタイル")
case (style, ("Pint" | "Half") as size): # ASパターン、("Pint" | "Half")がサブパターン
print(f"{style}を{size}サイズでください")
case (style, size):
print(f"{size}サイズはありません")
case {"beer": style, "size": size}: # マッピングパターン
print(f"{style}を{size}サイズでください")
まとめ
構造化パターンマッチングについて簡単に紹介しました。便利そうだなと思ってもらえたでしょうか?
単純なif文では不要ですが、複雑な条件分岐になりそうなときは、ぜひ構造化パターンマッチングを使ってみてください
参考
- What's New In Python 3.
10 - 作者のBrandt Bucher氏による発表動画
- 筆者によるPyCon Kyushu 2022 Kumamotoでの発表動画とスライド