パターンマッチング深掘り
基礎の match-case の次の段階 — クラスパターンと __match_args__、シーケンス/マッピングパターン、キャプチャとガード、そしてアンチパターンまでを整理します。
第3章 制御フロー で match-case を少し見ました。単純分岐、OR パターン、構造分解、クラスマッチング、ガード までです。本章はその次の段階です。すべてのパターンの種類を一か所にまとめ、__match_args__ のようなユーザー定義の統合点や、よく陥る落とし穴まで整理します。
本章のクラスパターンは 第8章 dataclass と一対です — @dataclass が __match_args__ を自動生成するからです。第9章 typing 本格 の discriminated union も、本章のマッピングパターンと組み合わせて使えます。
5つのパターンカテゴリ #
| 種類 | 形 | 意味 |
|---|---|---|
| リテラルパターン | case 200: | 正確な値の一致 |
| キャプチャパターン | case x: | 何にでもマッチして変数に束縛 |
| シーケンスパターン | case [a, b, *rest]: | list / tuple の形 |
| マッピングパターン | case {"key": v}: | dict の形 |
| クラスパターン | case Point(x=0): | クラス + 属性のマッチ |
この5つが組み合わさってほぼすべてのケースを表現します。一つずつ深く見ましょう。
リテラルと値の比較 #
リテラルパターンは == で比較されます。
match status:
case 200:
print("OK")
case "active":
print("active")
case True:
print("yes")
case None:
print("none")ドット記法 — 定数との比較 #
case ABC: のようにそのまま書くと キャプチャ変数 になります。定数比較をするには ドット (.) が入らなければなりません。
SUCCESS = 200
match code:
case SUCCESS: # ⚠ SUCCESS という変数に code を束縛!
print("ok")
case _:
print("else")SUCCESS がすべての値にマッチしてしまって、2つ目の case は決して実行されません。ツールが警告を出してくれます。
class Status:
SUCCESS = 200
NOT_FOUND = 404
match code:
case Status.SUCCESS:
print("ok")
case Status.NOT_FOUND:
print("not found")定数をマッチングに使うときは enum またはクラスの属性 として置くパターンが安全です。
from enum import Enum
class Status(Enum):
SUCCESS = 200
NOT_FOUND = 404
match status:
case Status.SUCCESS:
...
case Status.NOT_FOUND:
...キャプチャ変数 #
match command:
case x:
print(f"受け取ったコマンド: {x}")小文字(正確にはドットのない識別子)はキャプチャです。すべての値にマッチして 変数に束縛します。_(アンダースコア)だけ例外 — ワイルドカードなので束縛しません。
同じキャプチャ変数を2度使えない #
match (a, b):
case (x, x): # ✗ SyntaxError
...同じ変数名を1つのパターンの中に2度使うとエラーです。同じ値かを検査するには ガード を使ってください。
match (a, b):
case (x, y) if x == y:
...シーケンスパターン #
リストとタプルの 形 をパターンとして使えます。
match items:
case []:
print("空のリスト")
case [x]:
print(f"1 つだけ: {x}")
case [x, y]:
print(f"2 つ: {x}, {y}")
case [first, *rest]:
print(f"頭 {first}、残り {rest}")
case [*init, last]:
print(f"前 {init}、最後 {last}")
case [first, *middle, last]:
print(f"{first}, {middle}, {last}")*name で残りを一度に受け取れます — 通常の unpacking と同じです。マッチングは list と tuple の両方を受け取ります。 ただし str と bytes はシーケンスですがマッチングからは除外されます(意図しないマッチを防ぐため)。
match "hello":
case [a, *rest]:
print(a, rest) # マッチしない
case "hello":
print("hello!") # こちらへ長さ固定 vs 可変 #
[a, b, c] — 正確に長さ3
[a, b, *rest] — 3以上
[*items] — すべての長さ(事実上キャプチャと同じ)
マッピングパターン #
dict の 部分マッチング です。書いたキーがすべてあればマッチし、それ以外のキーは無視されます。
event = {"type": "click", "x": 10, "y": 20, "extra": "ignored"}
match event:
case {"type": "click", "x": x, "y": y}:
print(f"({x}, {y})")
case {"type": "key", "code": code}:
print(f"key {code}")マッピングパターンは部分マッチ です。{"type": "click"} は「type キーが click の dict なら OK」。他のキーがあってもマッチします。
**rest で残りを受け取る
#
match config:
case {"host": host, "port": port, **rest}:
print(f"{host}:{port}、それ以外のオプション: {rest}")キー自体はリテラルでなければならない #
match data:
case {key: value}: # ✗ key はキャプチャではない
...マッピングパターンの キー位置は常にリテラルまたはドット記法 です。キャプチャはできません。
クラスパターン #
第3章 制御フロー で短く見た形:
class Circle:
def __init__(self, radius: float):
self.radius = radius
match shape:
case Circle(radius=r):
print(f"円、半径 {r}")キーワード引数の形でマッチします。属性名 を使うわけです。
__match_args__ — 位置マッチング
#
Point(0, 0) のように 位置引数 でマッチするには、クラスが __match_args__ を教えてあげなければなりません。
class Point:
__match_args__ = ("x", "y")
def __init__(self, x: float, y: float):
self.x = x
self.y = y
match p:
case Point(0, 0):
print("原点")
case Point(0, y):
print(f"y 軸上、y={y}")
case Point(x, 0):
print(f"x 軸上、x={x}")
case Point(x, y):
print(f"({x}, {y})")@dataclass が自動生成
#
第8章 の @dataclass が __match_args__ を 自動で作ってくれます。 だから dataclass とパターンマッチングは相性抜群です。
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
match p:
case Point(0, 0): # 自動で動作
print("原点")ビルトインもマッチング可能 — ただし1引数のみ #
int、str、bool のようなビルトインは 位置1つ で値そのものをマッチします。
match x:
case int(n):
print(f"整数 {n}")
case str(s):
print(f"文字列 {s}")
case list() if not x: # 空リスト
print("空のリスト")int(n) は「int の値を n にキャプチャ」を意味します。
キャプチャ vs 比較 — as キーワード
#
マッチした全体の値を変数で受け取りたいときは as を使います。
match shape:
case Circle() as c:
register(c)
case Square(side=s) as sq if s > 10:
register(sq)元の値をもう一度参照する必要がある場合に有用です。
OR パターン — |
#
match status:
case 200 | 201 | 204:
print("成功")
case 400 | 404 | 422:
print("クライアントエラー")OR パターンの中では すべての変数が同じ変数をキャプチャ していなければなりません。
match val:
case [x] | [x, y]: # ✗ y が最初のパターンにない
...match val:
case [x] | [x, _]: # どちらも x だけキャプチャ
...ガード — if
#
第3章 で短く見ました。
match (x, y):
case (a, b) if a == b:
...
case (a, b) if a > b:
...ガードは すべてのパターン種別と組み合わせられます。キャプチャされた変数を見て追加条件を検査する用途です。
ワイルドカード — _
#
_ は キャプチャなしにマッチ します。case _: が default の役割をします。
match status:
case 200:
print("OK")
case _:
print("それ以外") # defaultまた 複数の位置に来ても衝突しません。
match items:
case [_, _, third]:
print(f"3 番目: {third}")x, x は不可ですが _, _ は可能です — _ はキャプチャしないからです。
組み合わせ例 — JSON 処理 #
API レスポンスをさまざまな形によって分岐する例です。第24章 Pydantic v2 深掘り の discriminated union と同じ問題を、別の方法で扱っています。
def handle(response: dict) -> str:
match response:
# 成功ケース — データがあって type が決まった形
case {"status": "ok", "data": {"items": list(items), "total": int(total)}}:
return f"受信: {len(items)} 個 / 全 {total}"
# ページネーションがある場合
case {"status": "ok", "data": {"items": items, "next_cursor": str(cursor)}}:
return f"次のページ cursor: {cursor}"
# 空の結果
case {"status": "ok", "data": {"items": []}}:
return "結果なし"
# エラー — コード + メッセージ
case {"status": "error", "code": int(code), "message": str(msg)}:
return f"エラー {code}: {msg}"
# それ以外
case _:
return "不明なレスポンス"同じ仕事を if/elif で解くと 型検査 + キー存在検査 + 値キャプチャがバラバラ に散らばります。match がその3つを1つにまとめてくれます。
match-case が合わない場合
#
万能ではありません。単純な if の方が読みやすい場合 も多いです。
match score:
case s if s >= 90:
grade = "A"
case s if s >= 80:
grade = "B"
case s if s >= 70:
grade = "C"
case _:
grade = "F"if score >= 90:
grade = "A"
elif score >= 80:
grade = "B"
elif score >= 70:
grade = "C"
else:
grade = "F"判断基準: 形で分岐するのか、値の範囲で分岐するのか。 形が鍵なら match、単純比較なら if/elif が普通は優れています。
練習問題 #
@dataclassでShapeの子Circle(radius: float)、Rectangle(width: float, height: float)、Triangle(base: float, height: float)の3つを作ってください。def area(s: Shape) -> float:をmatch一行で書いて各図形の面積を返します (__match_args__の自動生成を活用)。- API レスポンス dict
{"status": "ok", "data": [...]}/{"status": "error", "code": int, "message": str}の2形を処理する関数を書いてください。dataの中のリストが空の場合 / 100個以上の場合を追加分岐として分けます(マッピングパターン + シーケンスパターン + ガードの組み合わせ)。 - 旧スタイルのコード (
if isinstance(x, A): ...を5段以上書いたコード)を本章のmatchで書き直してみてください。行数がどれだけ減って意図がどれだけ明確になったかを比較します。
一行まとめ: パターン5つ — リテラル / キャプチャ / シーケンス / マッピング / クラス。定数比較はドット記法 (
Status.OK) 必須。シーケンスは list/tuple のみ、str/bytes は除外。マッピングは部分マッチ、**restで残り。クラスは__match_args__の位置マッチ (@dataclass自動)。asで全体キャプチャ、|で OR(同じ変数のみ)、ガードはすべてのパターンと組み合わせます。形での分岐は match、値の範囲での分岐は if/elif。
次の章 #
次の 第14章 非同期入門 (asyncio) が2部の最後 — asyncio と async/await を扱います。第11章 で短く見たコルーチンが本格的に登場する段階です。3部の第18章 非同期深掘り の土台になります。