目次
13 章

パターンマッチング深掘り

基礎の 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 またはクラスの属性 として置くパターンが安全です。

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度使えない #

🚫 1 つのパターンに同じ名前を 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 の両方を受け取ります。 ただし strbytes はシーケンスですがマッチングからは除外されます(意図しないマッチを防ぐため)。

文字列はシーケンスパターンにマッチしない
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__ を教えてあげなければなりません。

__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 とパターンマッチングは相性抜群です。

dataclass + match
from dataclasses import dataclass

@dataclass
class Point:
    x: float
    y: float

match p:
    case Point(0, 0):       # 自動で動作
        print("原点")

ビルトインもマッチング可能 — ただし1引数のみ #

intstrbool のようなビルトインは 位置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 を使います。

as
match shape:
    case Circle() as c:
        register(c)
    case Square(side=s) as sq if s > 10:
        register(sq)

元の値をもう一度参照する必要がある場合に有用です。

OR パターン — | #

OR
match status:
    case 200 | 201 | 204:
        print("成功")
    case 400 | 404 | 422:
        print("クライアントエラー")

OR パターンの中では すべての変数が同じ変数をキャプチャ していなければなりません。

🚫 異なるキャプチャは 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 と同じ問題を、別の方法で扱っています。

実戦 — API レスポンスの分岐
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
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/elif の方が自然
if score >= 90:
    grade = "A"
elif score >= 80:
    grade = "B"
elif score >= 70:
    grade = "C"
else:
    grade = "F"

判断基準: 形で分岐するのか、値の範囲で分岐するのか。 形が鍵なら match、単純比較なら if/elif が普通は優れています。

練習問題 #

  1. @dataclassShape の子 Circle(radius: float)Rectangle(width: float, height: float)Triangle(base: float, height: float) の3つを作ってください。def area(s: Shape) -> float:match 一行で書いて各図形の面積を返します (__match_args__ の自動生成を活用)。
  2. API レスポンス dict {"status": "ok", "data": [...]} / {"status": "error", "code": int, "message": str} の2形を処理する関数を書いてください。data の中のリストが空の場合 / 100個以上の場合を追加分岐として分けます(マッピングパターン + シーケンスパターン + ガードの組み合わせ)。
  3. 旧スタイルのコード (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部の最後 — asyncioasync/await を扱います。第11章 で短く見たコルーチンが本格的に登場する段階です。3部の第18章 非同期深掘り の土台になります。

X