2022年7月: 構造化パターンマッチング(takanory)¶
鈴木たかのりです。 今月からgihyo.jp上で「Python Monthly Topics」と題して、毎月Python関連の話題になったトピックやツール、ライブラリなどを紹介していきたいと思います。
第1回目はPython 3.10の新機能「構造化パターンマッチング(Structural Pattern Matching)」について紹介します。
Python 3.10の新機能¶
Python 3.10は2021年10月4日にリリースされました。 現在のPythonの最新バージョンは3.10.5でDownload Pythonのページからダウンロードできます。
Python 3.10.5のリリースページを見ると、謎のヘビの画像があります。
この画像はPython 3.10 release logoという画像で、ヘビの周りにPython 3.10の主な新機能が書いてあります。それは以下の5つです。
Parenthesized Context Managers
Better Typing Syntax
Better Error Messages
Structural Pattern Matching
Better Debugging
今回はそのうち「構造化パターンマッチング(Structural Pattern Matching)」を紹介します。
構造化パターンマッチングとは¶
構造化パターンマッチングはPythonの新しい文法です。
match
文とcase
文を使用して以下のようなコードが書けるようになります。
beer_style
変数の値によって処理が分岐し、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(Python Enhancement Proposal)というドキュメントを書いて、開発者間で議論して採択される必要があります。 構造化パターンマッチングはとても大きな機能のため、3つのPEPが存在します(通常は1つの機能に対して1つのPEPドキュメントです)。
この記事では一部機能しか紹介できないので、詳細な仕様やチュートリアルを確認したい方は、ぜひ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.3.1. キーワード(Python公式ドキュメント)
構造化パターンマッチングのなにが便利なのか¶
冒頭のコード例を見て「この機能のなにが便利なのか?if文でよいのでは?」と感じた方も多いと思います。 しかし、構造化パターンマッチングではパターンにさまざまな種類があり、if文では複雑になる条件分岐をすっきりと書けるようになります。
キャプチャーパターン¶
キャプチャーパターンではパターンにマッチしたときに変数に値が代入されます。 コード例を見てみましょう。
order = ("beer", "IPA") # ビールの注文
# order = ("food", "Pizza") # フードの注文
match order:
case ("beer", beer):
print(f"ビール「{beer}」をお持ちしました")
case ("food", food):
print(f"フード「{food}」を召し上がれ")
このコードを実行すると「ビール「IPA」をお持ちしました
」というメッセージが出力されます。
タプルの1つ目の要素が文字列の"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 --- データクラス(Python公式ドキュメント)
以下のような注文用の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文では不要ですが、複雑な条件分岐になりそうなときは、ぜひ構造化パターンマッチングを使ってみてください(Python 3.10以降の必要があります)。 コードの見通しがよくなると思います!
参考¶
作者のBrandt Bucher氏による発表動画
筆者によるPyCon Kyushu 2022 Kumamotoでの発表動画とスライド