Python 3.10以降のパターンマッチングとは?
Python
3.10で導入されたパターンマッチング機能は、従来のif-else文や辞書、関数による条件分岐を超えて、データの構造に基づいた柔軟で表現力豊かな条件分岐を可能にする強力なツールです。新しいmatch
文を使用することで、リストやタプルなどのシーケンスや、オブジェクトの属性に対する条件分岐が簡潔に記述できるようになりました。
この機能は、他の言語(特にScala
やRust
)で採用されているパターンマッチングに似ており、複雑なデータ構造の処理を大幅に簡素化します。
基本構文
パターンマッチングの基本的な構文は以下の通りです。
match 式:
case パターン1:
# パターン1にマッチした場合の処理
case パターン2:
# パターン2にマッチした場合の処理
case _:
# どのパターンにもマッチしない場合の処理
match
キーワードで始まり、評価したい式を指定します。case
キーワードで各パターンを定義し、それぞれに対応する処理を書きます。_
(アンダースコア)はワイルドカードとして機能し、どのパターンにもマッチしない場合のデフォルトの処理を表します。
基本的な使用例
例えば、数値の条件分岐をmatch
文で行う例を見てみましょう。
def describe_number(x):
match x:
case 0:
return "ゼロです"
case 1:
return "1です"
case _:
return "その他の数です"
print(describe_number(1)) # 出力: 1です
print(describe_number(10)) # 出力: その他の数です
この例では、引数x
が0
の場合には「ゼロです」、1
の場合には「1です」、それ以外の場合には「その他の数です」と表示されます。通常のif-else文よりも簡潔に、複数のケースを明示的に定義できます。
シーケンスのパターンマッチング
パターンマッチングの強力な点は、リストやタプルなどのシーケンスに対しても簡単にマッチングを行える点です。
リストのパターンマッチング
例えば、リストの要素数に応じて処理を変えたい場合、次のように書けます。
def handle_list(items):
match items:
case []:
print("リストは空です")
case [first]:
print(f"1つの要素: {first}")
case [first, second]:
print(f"2つの要素: {first}, {second}")
case [first, second, *rest]:
print(f"最初の2つの要素: {first}, {second}、残りの要素: {rest}")
handle_list([]) # 出力: リストは空です
handle_list([1]) # 出力: 1つの要素: 1
handle_list([1, 2]) # 出力: 2つの要素: 1, 2
handle_list([1, 2, 3, 4, 5]) # 出力: 最初の2つの要素: 1, 2、残りの要素: [3, 4, 5]
このように、リストのパターンマッチングでは、リストの長さや各要素に応じた処理を柔軟に記述できます。特に*rest
のように可変長の部分をマッチさせることで、リストの残りの要素をまとめて扱うことも可能です。
タプルのパターンマッチング
タプルや固定長のシーケンスに対しても同様にマッチングが行えます。
def handle_tuple(t):
match t:
case (x, y):
print(f"2つの要素: {x}, {y}")
case (x, y, z):
print(f"3つの要素: {x}, {y}, {z}")
case _:
print("その他のパターン")
handle_tuple((1, 2)) # 出力: 2つの要素: 1, 2
handle_tuple((1, 2, 3)) # 出力: 3つの要素: 1, 2, 3
handle_tuple((1, 2, 3, 4)) # 出力: その他のパターン
この例では、2つの要素を持つタプルと、3つの要素を持つタプルに対して異なる処理を行い、それ以外のパターンでは「その他のパターン」として処理しています。
オブジェクトのパターンマッチング
パターンマッチングは、カスタムクラスのオブジェクトに対しても有効です。オブジェクトの属性に基づいたマッチングが可能で、特定の属性に対する条件分岐を簡単に記述できます。
オブジェクトのマッチング例
例えば、以下のようにPoint
クラスを定義し、その属性に基づいて処理を行う例です。
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def handle_point(p):
match p:
case Point(x, y) if x == y:
print(f"正方形の対角線上の点: ({x}, {y})")
case Point(x, y):
print(f"異なる座標の点: ({x}, {y})")
p1 = Point(1, 1)
p2 = Point(1, 2)
handle_point(p1) # 出力: 正方形の対角線上の点: (1, 1)
handle_point(p2) # 出力: 異なる座標の点: (1, 2)
この例では、Point
クラスのオブジェクトp
に対して、属性x
とy
が等しい場合とそれ以外の場合で異なる処理を行っています。
パターンマッチングの応用例
辞書型のマッチング
パターンマッチングは辞書型のデータにも適用できます。例えば、APIレスポンスなどで辞書形式のデータが返ってくる場合、そのキーや値に基づいて処理を分岐させることができます。
def handle_response(response):
match response:
case {"status": 200, "data": data}:
print(f"成功: {data}")
case {"status": 404}:
print("エラー: データが見つかりません")
case _:
print("不明なエラー")
response_1 = {"status": 200, "data": "結果データ"}
response_2 = {"status": 404}
handle_response(response_1) # 出力: 成功: 結果データ
handle_response(response_2) # 出力: エラー: データが見つかりません
ここでは、ステータスコード200
の成功レスポンスと、404
のエラーレスポンスに応じて処理を分岐しています。
ネストした構造のマッチング
パターンマッチングは、ネストされた構造を持つデータにも対応できます。例えば、リストや辞書がネストしている場合でも、対応するパターンを定義して処理が可能です。
def handle_nested_structure(data):
match data:
case {"name": name, "details": {"age": age, "location": location}}:
print(f"名前: {name}, 年齢: {age}, 場所: {location}")
case _:
print("パターンにマッチしませんでした")
data = {
"name": "Alice",
"details": {
"age": 30,
"location": "Tokyo"
}
}
handle_nested_structure(data) # 出力: 名前: Alice, 年齢: 30, 場所: Tokyo
このように、ネストされた辞書の構造に基づいて、特定のキーにアクセスし、条件に応じた処理を行うことができます。
パターンマッチングの利点
- 可読性の向上
パターンマッチングを使うことで、従来のif-else文に比べて複雑な条件分岐をシンプルかつ明示的に記述できます。特にデータ構造が複雑な場合にその真価を発揮します。 - 構造化されたデータ処理
シーケンスやオブジェクト、辞書など、複雑なデータ構造を持つデータを簡単に分解して処理できるため、コードの保守性が向上します。 - 柔軟な条件分岐
条件に基づいた分岐だけでなく、値や構造、さらにはif
条件を組み合わせることで、非常に柔軟なロジックを簡潔に実装できます。
まとめ
Python
3.10で導入されたパターンマッチングは、従来の条件分岐を大幅に強化し、複雑なデータ構造をシンプルに扱えるようにします。リストやタプル、辞書、カスタムオブジェクトに対するマッチングが簡単になり、特にデータ処理やAPIレスポンスの解析などでその威力を発揮します。Python
3.10以降を使用する際には、この新しい強力な条件分岐ツールをぜひ活用してみてください。