Skip to content

Python match-case:構造的パターンマッチングを解説(Python 3.10+)

Updated on

Python開発者は、分岐ロジックを扱うために何十年もの間 if-elif チェーンに頼ってきました。変数を複数の値と比較したり、データの構造を調べたり、オブジェクト型に応じて処理を振り分けたりするには、冗長な条件分岐ブロックが必要になり、ケースが増えるほど読みづらくなります。10種類のコマンドタイプにディスパッチする関数は elif の壁になりがちで、繰り返し比較の下に本来のロジックが埋もれてしまいます。

これは読みやすさだけの問題ではありません。深い if-elif チェーンは、条件の置き間違い、ケースの書き忘れ、意図しない抜け(fall-throughのように見える流れ)によって、誤ったデータを下流へ静かに渡してしまうなど、微妙なバグの温床になりやすいのです。他の言語は何年も前からパターンマッチングの仕組みでこれを解決していましたが、Pythonには 3.10 までネイティブな解決策がありませんでした。

Python 3.10 では match-case 文(PEP 634)が導入され、言語に構造的パターンマッチングが加わりました。これは単なる値比較を超えた機能です。シーケンスの分解、辞書の形のマッチ、変数束縛、ガード条件、クラス型によるディスパッチまで、クリーンで宣言的な構文で書けます。本ガイドでは、すべてのパターン種類を実用例とともに解説します。

📚

構造的パターンマッチングとは?

構造的パターンマッチングは、ある値(「subject(対象)」)を一連のパターンと照合し、最初に一致したパターンのコードブロックを実行する仕組みです。CやJavaのswitch文と違い、Pythonのmatch-caseは値の比較だけを行うのではなく、データの「構造」を検査します。

基本構文:

match subject:
    case pattern1:
        # code for pattern1
    case pattern2:
        # code for pattern2
    case _:
        # default case (wildcard)

主な特徴:

  • パターンは上から下へ評価されます。最初に一致したパターンが採用されます。
  • ワイルドカードパターン _ は何にでも一致し、デフォルトケースとして機能します。
  • パターンは変数を束縛できます。つまり、一致した値の一部を名前に取り込み、そのcaseブロック内で利用できます。
  • フォールスルー(fall-through)はありません。実行されるcaseブロックは1つだけです。

Pythonのバージョン要件: match-case は Python 3.10 以降が必要です。python --version で確認できます。古いバージョンの場合は、この機能を使う前にアップグレードが必要です。

リテラルパターン:完全一致でのマッチ

match-case の最も基本的な使い方は、値をリテラル定数と照合することです。

def get_http_status_message(code):
    match code:
        case 200:
            return "OK"
        case 201:
            return "Created"
        case 301:
            return "Moved Permanently"
        case 400:
            return "Bad Request"
        case 403:
            return "Forbidden"
        case 404:
            return "Not Found"
        case 500:
            return "Internal Server Error"
        case _:
            return f"Unknown status code: {code}"
 
print(get_http_status_message(404))  # Not Found
print(get_http_status_message(418))  # Unknown status code: 418

リテラルパターンは、整数、文字列、真偽値、None に対応します。

def describe_value(value):
    match value:
        case True:
            return "Boolean true"
        case False:
            return "Boolean false"
        case None:
            return "None value"
        case "":
            return "Empty string"
        case 0:
            return "Zero"
        case _:
            return f"Other: {value}"

重要: TrueFalseNone は、等価比較ではなく同一性(is のように)でマッチします。つまり、Pythonでは 1 == TrueTrue ですが、match 1case True に一致しません。

Orパターン:複数の値にマッチ

パイプ演算子 | を使うと、1つのcaseで複数パターンのいずれかに一致させられます。

def classify_day(day):
    match day.lower():
        case "monday" | "tuesday" | "wednesday" | "thursday" | "friday":
            return "Weekday"
        case "saturday" | "sunday":
            return "Weekend"
        case _:
            return "Invalid day"
 
print(classify_day("Saturday"))  # Weekend
print(classify_day("Wednesday")) # Weekday

Orパターンはリテラルだけでなく、あらゆるパターン種別で使えます。

def categorize_error(code):
    match code:
        case 400 | 401 | 403 | 404 | 405:
            return "Client error"
        case 500 | 502 | 503 | 504:
            return "Server error"
        case _:
            return "Other"

変数キャプチャ(束縛)パターン

パターンは、一致した値の一部を変数として捕捉できます。パターン内の「素の名前(bare name)」は変数として扱われ、その位置に現れた値が束縛されます。

def parse_command(command):
    match command.split():
        case ["quit"]:
            return "Exiting program"
        case ["hello", name]:
            return f"Hello, {name}!"
        case ["add", x, y]:
            return f"Sum: {int(x) + int(y)}"
        case _:
            return "Unknown command"
 
print(parse_command("hello Alice"))  # Hello, Alice!
print(parse_command("add 3 5"))      # Sum: 8
print(parse_command("quit"))         # Exiting program

case ["hello", name] では、変数 name がリストの2番目の要素を捕捉します。これはリテラルマッチとは本質的に異なり、name という既存変数と比較しているわけではなく、新しい束縛を作っています。

キャプチャ vs 定数の落とし穴

素の名前はキャプチャパターンなので、変数の値と直接比較する目的では使えません。

QUIT_COMMAND = "quit"
 
match user_input:
    case QUIT_COMMAND:  # これは比較ではなく、QUIT_COMMANDへ束縛してしまう!
        print("This always matches!")

定数として比較したい場合は、ドット付き名(dotted name)かリテラルを使います。

class Commands:
    QUIT = "quit"
    HELP = "help"
 
match user_input:
    case Commands.QUIT:  # ドット付き名:値を参照して比較する
        print("Quitting")
    case Commands.HELP:
        print("Showing help")

. を含む名前はキャプチャではなく値参照として扱われます。これは PEP 634 の意図的な設計です。

シーケンスパターン:リストやタプルの分解

match-case はシーケンスの分解が得意です。リストやタプルの長さと中身を同時にマッチできます。

def process_point(point):
    match point:
        case (0, 0):
            return "Origin"
        case (x, 0):
            return f"On x-axis at {x}"
        case (0, y):
            return f"On y-axis at {y}"
        case (x, y):
            return f"Point at ({x}, {y})"
        case _:
            return "Not a valid point"
 
print(process_point((0, 0)))   # Origin
print(process_point((5, 0)))   # On x-axis at 5
print(process_point((3, 7)))   # Point at (3, 7)

可変長シーケンスのためのスター(*)パターン

* を使うと可変長のシーケンスにマッチできます。

def analyze_sequence(seq):
    match seq:
        case []:
            return "Empty sequence"
        case [single]:
            return f"Single element: {single}"
        case [first, second]:
            return f"Pair: {first}, {second}"
        case [first, *middle, last]:
            return f"First: {first}, Last: {last}, Middle has {len(middle)} items"
 
print(analyze_sequence([]))              # Empty sequence
print(analyze_sequence([42]))            # Single element: 42
print(analyze_sequence([1, 2, 3, 4, 5])) # First: 1, Last: 5, Middle has 3 items

ネストしたシーケンスパターン

パターンはネストでき、複雑なデータ構造にマッチできます。

def process_matrix_row(row):
    match row:
        case [[a, b], [c, d]]:
            return f"2x2 block: {a}, {b}, {c}, {d}"
        case [first_row, *rest]:
            return f"First row: {first_row}, remaining rows: {len(rest)}"
 
print(process_matrix_row([[1, 2], [3, 4]]))
# 2x2 block: 1, 2, 3, 4

マッピングパターン:辞書のマッチ

マッピングパターンは、辞書(のように振る舞うオブジェクト)が特定のキーを持つかを確認し、必要なら値を捕捉します。

def handle_api_response(response):
    match response:
        case {"status": "success", "data": data}:
            return f"Success! Data: {data}"
        case {"status": "error", "message": msg}:
            return f"Error: {msg}"
        case {"status": "error", "code": code, "message": msg}:
            return f"Error {code}: {msg}"
        case {"status": status}:
            return f"Unknown status: {status}"
        case _:
            return "Invalid response format"
 
print(handle_api_response({"status": "success", "data": [1, 2, 3]}))
# Success! Data: [1, 2, 3]
 
print(handle_api_response({"status": "error", "message": "Not found"}))
# Error: Not found

マッピングパターンは、指定したキーが含まれていればマッチし、余分なキーは無視されます。そのためJSONデータやAPIレスポンスの処理に向いています。

**rest で残りのキーを捕捉できます。

def extract_config(config):
    match config:
        case {"host": host, "port": port, **rest}:
            return f"Server: {host}:{port}, extra config: {rest}"
        case _:
            return "Missing required config"
 
print(extract_config({"host": "localhost", "port": 8080, "debug": True}))
# Server: localhost:8080, extra config: {'debug': True}

ガード節:パターンに条件を追加する

ガードは if 条件で、パターンがまず一致し、その後ガード式が評価されて真の場合のみマッチ成立となります。

def classify_number(n):
    match n:
        case x if x < 0:
            return "Negative"
        case 0:
            return "Zero"
        case x if x % 2 == 0:
            return "Positive even"
        case x if x % 2 == 1:
            return "Positive odd"
 
print(classify_number(-5))  # Negative
print(classify_number(0))   # Zero
print(classify_number(4))   # Positive even
print(classify_number(7))   # Positive odd

パターンだけでは条件を表現しきれない場合、ガードが重要になります。

def validate_age(data):
    match data:
        case {"name": name, "age": age} if age < 0:
            return f"Invalid: {name} has negative age"
        case {"name": name, "age": age} if age < 18:
            return f"{name} is a minor (age {age})"
        case {"name": name, "age": age} if age >= 18:
            return f"{name} is an adult (age {age})"
        case _:
            return "Invalid data format"
 
print(validate_age({"name": "Alice", "age": 25}))
# Alice is an adult (age 25)

クラスパターン:オブジェクト型のマッチ

クラスパターンは、値が特定クラスのインスタンスかどうかをチェックし、必要に応じて属性を分解します。

from dataclasses import dataclass
 
@dataclass
class Point:
    x: float
    y: float
 
@dataclass
class Circle:
    center: Point
    radius: float
 
@dataclass
class Rectangle:
    top_left: Point
    width: float
    height: float
 
def describe_shape(shape):
    match shape:
        case Circle(center=Point(x=0, y=0), radius=r):
            return f"Circle at origin with radius {r}"
        case Circle(center=center, radius=r) if r > 100:
            return f"Large circle at ({center.x}, {center.y})"
        case Circle(center=center, radius=r):
            return f"Circle at ({center.x}, {center.y}) with radius {r}"
        case Rectangle(width=w, height=h) if w == h:
            return f"Square with side {w}"
        case Rectangle(top_left=tl, width=w, height=h):
            return f"Rectangle at ({tl.x}, {tl.y}), {w}x{h}"
        case _:
            return "Unknown shape"
 
print(describe_shape(Circle(Point(0, 0), 5)))
# Circle at origin with radius 5
 
print(describe_shape(Rectangle(Point(1, 2), 10, 10)))
# Square with side 10

__match_args__ がないクラスではキーワード引数を使います。dataclasses や named tuple は __match_args__ を自動設定するため、位置引数パターンが使えます。

@dataclass
class Color:
    r: int
    g: int
    b: int
 
def describe_color(color):
    match color:
        case Color(0, 0, 0):
            return "Black"
        case Color(255, 255, 255):
            return "White"
        case Color(r, 0, 0):
            return f"Red shade (r={r})"
        case Color(0, g, 0):
            return f"Green shade (g={g})"
        case Color(0, 0, b):
            return f"Blue shade (b={b})"
        case Color(r, g, b):
            return f"RGB({r}, {g}, {b})"
 
print(describe_color(Color(255, 0, 0)))   # Red shade (r=255)
print(describe_color(Color(128, 64, 32))) # RGB(128, 64, 32)

組み込み型のマッチ

組み込み型を使って型チェックとしてマッチさせることもできます。

def process_value(value):
    match value:
        case int(n):
            return f"Integer: {n}"
        case float(n):
            return f"Float: {n}"
        case str(s):
            return f"String: '{s}'"
        case list(items):
            return f"List with {len(items)} items"
        case dict(d):
            return f"Dict with keys: {list(d.keys())}"
        case _:
            return f"Other type: {type(value).__name__}"
 
print(process_value(42))          # Integer: 42
print(process_value("hello"))     # String: 'hello'
print(process_value([1, 2, 3]))   # List with 3 items

match-case と if-elif の比較:使い分け

Criteriamatch-caseif-elif
Python version3.10+ onlyAll versions
Value comparisonClean, one value per caseVerbose with repeated variable
DestructuringBuilt-in (sequences, dicts, objects)Manual unpacking required
Type checkingNative class patternsRequires isinstance() calls
Guard conditionsif after patternStandard if/elif
Variable bindingAutomatic captureManual assignment
PerformanceOptimized pattern dispatchSequential evaluation
ReadabilityBetter for 4+ cases with structureBetter for 1-3 simple conditions
Fall-throughNo (by design)Possible with missing elif

match-case が明確に有利な場面

# Parsing structured data -- match-case
def handle_event(event):
    match event:
        case {"type": "click", "x": x, "y": y}:
            process_click(x, y)
        case {"type": "keypress", "key": key, "modifiers": [*mods]}:
            process_key(key, mods)
        case {"type": "scroll", "delta": delta} if delta != 0:
            process_scroll(delta)

同じロジックを if-elif で書くと次のように冗長になります。

# Same logic with if-elif -- more verbose
def handle_event(event):
    if event.get("type") == "click" and "x" in event and "y" in event:
        process_click(event["x"], event["y"])
    elif event.get("type") == "keypress" and "key" in event and "modifiers" in event:
        process_key(event["key"], event["modifiers"])
    elif event.get("type") == "scroll" and event.get("delta", 0) != 0:
        process_scroll(event["delta"])

if-elif のほうが良い場面

# Simple range checks -- if-elif is clearer
if temperature > 100:
    status = "boiling"
elif temperature > 50:
    status = "hot"
elif temperature > 20:
    status = "warm"
else:
    status = "cold"

match-case はパターンだけで範囲比較を直接表現できません。各caseにガードを書く必要があり、可読性の利点が薄れます。

実務でのユースケース

コマンドライン引数のパース

import sys
 
def main():
    match sys.argv[1:]:
        case ["help"]:
            print("Available commands: help, version, run, test")
        case ["version"]:
            print("v1.0.0")
        case ["run", filename]:
            print(f"Running {filename}")
        case ["run", filename, "--verbose"]:
            print(f"Running {filename} with verbose output")
        case ["test", *test_files] if test_files:
            print(f"Testing: {', '.join(test_files)}")
        case ["test"]:
            print("Running all tests")
        case [unknown, *_]:
            print(f"Unknown command: {unknown}")
        case []:
            print("No command provided. Use 'help' for usage.")

ステートマシンの実装

from dataclasses import dataclass
from typing import Optional
 
@dataclass
class State:
    name: str
    data: Optional[dict] = None
 
def transition(state, event):
    match (state.name, event):
        case ("idle", "start"):
            return State("loading", {"progress": 0})
        case ("loading", "progress"):
            progress = state.data.get("progress", 0) + 25
            if progress >= 100:
                return State("ready", {"progress": 100})
            return State("loading", {"progress": progress})
        case ("loading", "cancel"):
            return State("idle")
        case ("ready", "process"):
            return State("processing", state.data)
        case ("processing", "complete"):
            return State("done", {"result": "success"})
        case ("processing", "error"):
            return State("error", {"message": "Processing failed"})
        case (_, "reset"):
            return State("idle")
        case (current, unknown_event):
            print(f"No transition from '{current}' on '{unknown_event}'")
            return state
 
# Usage
state = State("idle")
for event in ["start", "progress", "progress", "progress", "progress", "process", "complete"]:
    state = transition(state, event)
    print(f"Event: {event} -> State: {state.name}, Data: {state.data}")

JSON APIレスポンスのハンドラ

def process_api_result(response):
    match response:
        case {"data": {"users": [*users]}, "meta": {"total": total}}:
            print(f"Found {total} users, received {len(users)}")
            for user in users:
                match user:
                    case {"name": name, "role": "admin"}:
                        print(f"  Admin: {name}")
                    case {"name": name, "role": role}:
                        print(f"  {role.title()}: {name}")
 
        case {"data": {"users": []}}:
            print("No users found")
 
        case {"error": {"code": code, "message": msg}} if code >= 500:
            print(f"Server error ({code}): {msg}")
            raise RuntimeError(msg)
 
        case {"error": {"code": code, "message": msg}}:
            print(f"Client error ({code}): {msg}")
 
        case _:
            print(f"Unexpected response format: {type(response)}")
 
# Example
process_api_result({
    "data": {"users": [
        {"name": "Alice", "role": "admin"},
        {"name": "Bob", "role": "editor"}
    ]},
    "meta": {"total": 2}
})

設定ファイルのパーサ

def apply_config(settings):
    for key, value in settings.items():
        match (key, value):
            case ("database", {"host": host, "port": int(port), "name": db}):
                print(f"DB connection: {host}:{port}/{db}")
            case ("database", _):
                raise ValueError("Invalid database config: need host, port, name")
            case ("logging", {"level": level}) if level in ("DEBUG", "INFO", "WARNING", "ERROR"):
                print(f"Log level set to {level}")
            case ("logging", {"level": level}):
                raise ValueError(f"Invalid log level: {level}")
            case ("features", {"enabled": list(features)}):
                print(f"Enabled features: {', '.join(features)}")
            case ("features", {"enabled": _}):
                raise TypeError("Features must be a list")
            case (key, _):
                print(f"Unknown config key: {key}")
 
apply_config({
    "database": {"host": "localhost", "port": 5432, "name": "mydb"},
    "logging": {"level": "INFO"},
    "features": {"enabled": ["auth", "cache", "metrics"]}
})

データ変換パイプライン

def transform_record(record):
    """Transform raw data records into standardized format."""
    match record:
        case {"timestamp": ts, "value": float(v) | int(v), "unit": str(unit)}:
            return {"time": ts, "measurement": float(v), "unit": unit.lower()}
 
        case {"timestamp": ts, "value": str(v), "unit": str(unit)}:
            try:
                return {"time": ts, "measurement": float(v), "unit": unit.lower()}
            except ValueError:
                return {"time": ts, "measurement": None, "unit": unit.lower(), "error": f"Cannot parse: {v}"}
 
        case {"timestamp": ts, "value": v}:
            return {"time": ts, "measurement": float(v), "unit": "unknown"}
 
        case {"values": [*values]} if values:
            return [transform_record({"timestamp": None, "value": v, "unit": "batch"}) for v in values]
 
        case _:
            return {"error": f"Unrecognized format: {record}"}
 
# Examples
print(transform_record({"timestamp": "2026-01-01", "value": 42.5, "unit": "Celsius"}))
print(transform_record({"timestamp": "2026-01-01", "value": "98.6", "unit": "F"}))

高度なパターンテクニック

ASパターン:一致全体を束縛する

as を使うと、分解しつつ一致した全体も捕捉できます。

def process_item(item):
    match item:
        case {"name": str(name), "price": float(price)} as product if price > 100:
            print(f"Premium product: {product}")
        case {"name": str(name), "price": float(price)} as product:
            print(f"Standard product: {product}")

パターン種別の組み合わせ

パターンは自然に合成できます。シーケンス、マッピング、クラスパターンを混ぜられます。

@dataclass
class Order:
    items: list
    customer: dict
 
def process_order(order):
    match order:
        case Order(items=[single_item], customer={"vip": True}):
            print(f"VIP single-item order: {single_item}")
        case Order(items=[_, _, *rest], customer={"name": name}) if len(rest) > 0:
            print(f"{name} ordered {2 + len(rest)} items")
        case Order(items=[], customer=_):
            print("Empty order")

Jupyter Notebooksでmatch-caseを使う

パターンマッチングは、異なるデータ形式やAPIレスポンスを扱い分ける必要があるデータ分析ワークフローで特に有用です。Jupyter notebooks でデータセットを対話的に探索する際、match-case は遭遇する多様なデータ形状をきれいに扱う手段になります。

def classify_cell_output(output):
    """Classify Jupyter cell output by type."""
    match output:
        case {"output_type": "stream", "text": text}:
            return f"Text output: {len(text)} chars"
        case {"output_type": "error", "ename": name, "evalue": value}:
            return f"Error: {name}: {value}"
        case {"output_type": "display_data", "data": {"image/png": _}}:
            return "Image output"
        case {"output_type": "execute_result", "data": {"text/html": html}}:
            return "HTML table output"
        case _:
            return "Unknown output type"

ノートブック上で、パターンマッチングやその他のPython 3.10+機能を使いながらAI支援も欲しいデータサイエンティストには、RunCell (opens in a new tab) がJupyter内に直接AIエージェントを提供し、実際のデータ構造に基づいてmatch-case文の作成・デバッグ・最適化を支援します。

よくある間違いと回避方法

間違い1:Python 3.10未満でmatch-caseを使う

# This fails with SyntaxError on Python 3.9 and earlier
match value:
    case 1:
        print("one")

対処: sys.version_info >= (3, 10) を確認するか、Pythonをアップグレードしてください。

間違い2:case内の名前を「比較」だと思ってしまう

expected = 200
 
match status_code:
    case expected:  # WRONG: creates a new variable 'expected'
        print("OK")

対処: ドット付き名、リテラル、またはガードを使います。

match status_code:
    case code if code == expected:
        print("OK")

間違い3:ワイルドカードケースを忘れる

case _ がないと、一致しない値は黙って何も起こりません。

match command:
    case "start":
        run()
    case "stop":
        halt()
    # If command is "pause", nothing happens -- no error, no warning

対処: 予期しない値を明示的に扱うため、ワイルドカードケースを入れましょう。

match command:
    case "start":
        run()
    case "stop":
        halt()
    case _:
        raise ValueError(f"Unknown command: {command}")

間違い4:順序依存のパターンで、具体性の低いものを先に置く

パターンは上から下へチェックされます。広いパターンが先にあると、後の具体的パターンが到達不能になります。

# WRONG: the first case always matches
match point:
    case (x, y):       # Captures everything
        print("generic")
    case (0, 0):       # Never reached
        print("origin")

対処: 具体的なパターンを先に置きます。

match point:
    case (0, 0):
        print("origin")
    case (x, y):
        print(f"point at ({x}, {y})")

パフォーマンスに関する考慮

match-case は効率的なバイトコードにコンパイルされます。単純なリテラルマッチでは、コンパイラが辞書ルックアップに近い形で最適化します。構造的パターンでは、冗長なチェックを避ける決定木が生成されます。

ベンチマークでは、少数ケース(5未満)では match-case と if-elif チェーンは同程度の性能です。より大きなディスパッチテーブル(10以上)では、特にリテラルパターンで、バイトコード最適化により match-case のほうが速くなることがあります。

ただし、単に値から関数へ対応付けるだけなら、パターンマッチングは辞書ディスパッチの代替ではありません。

# For simple value-to-action mapping, a dict is cleaner and faster
handlers = {
    "click": handle_click,
    "scroll": handle_scroll,
    "keypress": handle_keypress,
}
 
handler = handlers.get(event_type, handle_unknown)
handler(event_data)

構造の検査、分解、ガード条件など、辞書ルックアップでは表現できないことが必要なときに match-case を使いましょう。

FAQ

match-caseにはどのPythonバージョンが必要ですか?

match-case文には Python 3.10 以降が必要です。これは2021年10月にリリースされたPython 3.10の一部として、PEP 634で導入されました。Python 3.9以前でmatch-caseを使うと SyntaxError になります。端末で python --version を実行してバージョンを確認できます。

Pythonのmatch-caseは他言語のswitch-caseと同じですか?

いいえ。Pythonのmatch-caseは構造的パターンマッチングであり、従来のswitch-caseより強力です。CやJavaのswitch-caseが値比較だけを行うのに対し、Pythonのmatch-caseはシーケンスや辞書の分解、属性によるクラスインスタンスのマッチ、マッチしたデータからの変数束縛、ガード条件の適用ができます。Rust、Scala、Haskellなどのパターンマッチングにより近いものです。

match-caseにはCのswitchのようなfall-throughがありますか?

いいえ。Pythonのmatch-caseは最初に一致したcaseブロックだけを実行してmatch文を抜けます。fall-throughはなく、break文も不要です。複数パターンで同じ処理を実行したい場合は、パイプ演算子を使ったorパターンを使います。たとえば case "a" | "b" | "c": はこれらのいずれかに一致します。

match-caseで正規表現は使えますか?

直接は使えません。match-caseのパターンは構造的であり、正規表現ベースではありません。ただしガード節を使って正規表現マッチを適用できます。case str(s) if re.match(r"pattern", s): のように、構造的な型チェックとガードでの正規表現検証を組み合わせられます。

match-caseはNoneをどう扱いますか?

Noneは case None: というリテラルパターンでマッチします。NoneはPythonのシングルトンなので、マッチは同一性比較(is None 相当)になります。つまり case None: は実際のNoneオブジェクトにのみ一致し、0、False、空文字など他のfalsy値には一致しません。

どのcaseにも一致しなかったらどうなりますか?

どのcaseにも一致せず、かつワイルドカードパターン(case _:)もない場合、match文は黙って何もしません。その後の処理が継続し、例外も発生しません。このため、予期しない値に対してデフォルト動作や例外を明示する目的で、ワイルドカードcaseを入れるのが良いプラクティスです。

まとめ

Pythonのmatch-case文は、複雑な分岐ロジックを長年 if-elif チェーンに頼ってきた言語に、構造的パターンマッチングをもたらしました。データ形状の検査、シーケンスや辞書の分解、オブジェクト型によるディスパッチ、変数束縛を、同等の条件分岐よりも読みやすい宣言的な構文で実現できます。

重要なのは、match-caseは単なるswitch文ではないという点です。構造化データを扱うための道具であり、コマンドの解析、APIレスポンスの処理、ステートマシンの実装、オブジェクト型に基づくルーティングなどに適しています。単純な値比較ではなく「データの形」をチェックする分岐では、match-caseはより短く、保守しやすいコードになります。

まずは単純なリテラルパターンから始め、必要に応じてシーケンス分解、マッピングパターン、クラスパターンへ段階的に広げてください。予期しない入力を明示的に扱うためにワイルドカードcaseを入れること、具体的なパターンを一般的なパターンより先に置くこと、そして素の名前は比較ではなく値をキャプチャする点を忘れないことが重要です。

自分のデータでmatch-caseパターンを対話的に探るには、Jupyter notebooks が理想的な環境です。RunCell (opens in a new tab) はPython 3.10+の機能を理解するAI支援でこのワークフローを強化し、データ形状に基づいたパターン構造の提案も行えます。

📚