Skip to content
トピック
Python
Python Switch Case: match-case文をわかりやすく解説

Python Switch Case: match-case文をわかりやすく解説

更新日

Python でコマンドプロセッサを書いているとします。関数は "quit""save""load""help" のような文字列を受け取り、それぞれに応じた処理を実行する必要があります。JavaScript や C なら switch 文を使うでしょう。Python では if/elif/else を連ねて書きます。2 つのコマンドなら 5 行、6 つなら 15 行、12 つなら 30 行にもなります。関数は比較だらけの壁のようになり、読みづらく、順序を間違えやすく、拡張も面倒です。

If your search query is python switch case, start here. For the deeper structural-pattern-matching reference, see Python Match Case. For a simpler dictionary-dispatch overview, see Python Switch Case.

これは Python にとって 30 年以上にわたる実際の制約でした。主要な他言語には switchcase 構文がありましたが、Python は if/elif の連鎖と辞書ディスパッチに完全に依存していました。Guido van Rossum は switch-case の提案を何度も退け、if/elif で十分だと主張していました。

その状況は Python 3.10PEP 634 (opens in a new tab) により変わりました。match-case 文が導入され、Python における switch-case の答えとなったのです。ただし、はるかに強力です。単に値を一致させるだけでなく、オブジェクトを分解し、パターンを照合し、変数を束縛し、条件を適用できます。これは単なる switch の置き換えではなく、構造的パターンマッチングです。

30 秒でわかる基本構文: match-case

def handle_command(command):
    match command:
        case "quit":
            print("Exiting program")
        case "save":
            print("Saving file")
        case "help":
            print("Showing help")
        case _:
            print(f"Unknown command: {command}")
 
handle_command("save")   # Saving file
handle_command("hello")  # Unknown command: hello

match キーワードは対象式を 1 回だけ評価します。各 case はパターンを定義します。Python は上から順にパターンを試し、最初に一致したものを実行します。ワイルドカード _ はデフォルトケースで、あらゆる値に一致します。

match-case と switch-case の主な違い

C、Java、JavaScript、Go に慣れているなら、重要な違いは次のとおりです。

機能Python match-caseC/Java switch-case
フォールスルーなし(各 case は独立)あり(break が必要)
パターンの種類リテラル、シーケンス、マッピング、クラス、OR、ガード主にリテラルのみ
分解標準で可能(構造から変数を束縛)非対応
変数束縛一致したパターンから値を取り出せる非対応
デフォルトケースcase _:default:
式/文文(戻り値はない)
最小 Python バージョン3.10+N/A

最も重要な違いは フォールスルーがない ことです。各 case ブロックは独立しています。break を書く必要はなく、次の case を誤って実行することもありません。

パターンの種類: match-case で何ができるか

Python の match-case は 7 種類のパターンをサポートしています。これが、従来の switch 文より本質的に強力な理由です。

1. リテラルパターン

文字列、数値、真偽値、None などの正確な値に一致させます。

def classify_status(code):
    match code:
        case 200:
            return "OK"
        case 301:
            return "Moved Permanently"
        case 404:
            return "Not Found"
        case 500:
            return "Internal Server Error"
        case _:
            return f"Unknown status: {code}"
 
print(classify_status(404))  # Not Found

2. OR パターン(複数値)

| 演算子を使って、1 つの case で複数の値に一致させます。

def classify_http_status(code):
    match code:
        case 200 | 201 | 204:
            return "Success"
        case 301 | 302 | 307 | 308:
            return "Redirect"
        case 400 | 401 | 403 | 404:
            return "Client Error"
        case 500 | 502 | 503:
            return "Server Error"
        case _:
            return "Unknown"

これは if code in (200, 201, 204): のような定番パターンを置き換えます。

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

case に裸の名前を書くと、一致した値を変数として受け取ります。

def process_value(data):
    match data:
        case 0:
            print("Zero")
        case value:
            print(f"Got non-zero value: {value}")
 
process_value(42)  # Got non-zero value: 42

重要な落とし穴: 裸の名前は常にキャプチャであり、既存の変数と比較するものではありません。これは最もよくある間違いです。

# WRONG — this does NOT compare against expected_code
expected_code = 200
match response_code:
    case expected_code:  # This captures response_code into expected_code!
        print("Match")
 
# RIGHT — use a guard or literal
match response_code:
    case code if code == expected_code:
        print("Match")

定数と比較したい場合は、ドット付きの名前(http.HTTPStatus.OK など)かガード節を使ってください。

4. シーケンスパターン(リストとタプル)

リストやタプルをパターン内で直接分解します。

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})"
 
print(process_point((3, 0)))   # On x-axis at 3
print(process_point((2, 5)))   # Point at (2, 5)

*rest を使って残りの要素を受け取ることもできます。

def first_and_rest(items):
    match items:
        case []:
            return "Empty list"
        case [single]:
            return f"Single item: {single}"
        case [first, *rest]:
            return f"First: {first}, remaining: {rest}"
 
print(first_and_rest([1, 2, 3, 4]))  # First: 1, remaining: [2, 3, 4]

5. マッピングパターン(辞書)

辞書のキーに一致させ、値を分解します。

def handle_event(event):
    match event:
        case {"type": "click", "x": x, "y": y}:
            print(f"Click at ({x}, {y})")
        case {"type": "keypress", "key": key}:
            print(f"Key pressed: {key}")
        case {"type": "scroll", "direction": direction}:
            print(f"Scroll {direction}")
        case _:
            print("Unknown event")
 
handle_event({"type": "click", "x": 100, "y": 200})
# Click at (100, 200)
 
handle_event({"type": "keypress", "key": "Enter", "modifiers": ["Ctrl"]})
# Key pressed: Enter  (extra keys are ignored)

マッピングパターンは、指定したキーが存在すれば一致します。辞書に余分なキーがあっても無視されます。JSON データや API レスポンスの処理に適しています。

6. クラスポターン

クラスのインスタンスに一致させ、属性を取り出します。

from dataclasses import dataclass
 
@dataclass
class Point:
    x: float
    y: float
 
@dataclass
class Circle:
    center: Point
    radius: float
 
@dataclass
class Rectangle:
    corner: 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):
            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(width=w, height=h):
            return f"Rectangle {w}x{h}"
 
print(describe_shape(Circle(Point(0, 0), 5)))
# Circle at origin with radius 5
 
print(describe_shape(Rectangle(Point(1, 1), 4, 4)))
# Square with side 4

7. ガード節

任意のパターンに if 条件を追加して、さらに絞り込みできます。

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

ガードにより、パターンだけでは表現できない任意の条件を追加できます。まずパターンが一致し、その後にガード条件が評価されます。

実践例: JSON API レスポンスパーサー

match-case の最も実用的な用途の 1 つは、API レスポンスや JSON データの解析です。

import json
 
def process_api_response(response: dict) -> str:
    match response:
        case {"status": "success", "data": {"users": [*users]}}:
            return f"Found {len(users)} users"
 
        case {"status": "success", "data": {"user": {"name": name, "email": email}}}:
            return f"User: {name} ({email})"
 
        case {"status": "error", "code": 401}:
            return "Authentication required"
 
        case {"status": "error", "code": code, "message": msg}:
            return f"Error {code}: {msg}"
 
        case {"status": "error"}:
            return "Unknown error"
 
        case _:
            return "Unrecognized response format"
 
# Test with different responses
print(process_api_response({
    "status": "success",
    "data": {"users": ["Alice", "Bob", "Charlie"]}
}))
# Found 3 users
 
print(process_api_response({
    "status": "error",
    "code": 429,
    "message": "Rate limit exceeded"
}))
# Error 429: Rate limit exceeded

これは、深くネストした if/elif チェーンと isinstance() チェック、辞書の .get() 呼び出しを置き換えます。match-case 版では、期待する構造をそのまま記述できます。

実践例: コマンドライン引数パーサー

import sys
 
def parse_args(args: list[str]):
    match args:
        case [program]:
            print(f"Usage: {program} <command> [options]")
 
        case [_, "init", project_name]:
            print(f"Initializing project: {project_name}")
 
        case [_, "init"]:
            print("Error: project name required")
 
        case [_, "build", "--release"]:
            print("Building in release mode")
 
        case [_, "build"]:
            print("Building in debug mode")
 
        case [_, "test", *test_files] if test_files:
            print(f"Running tests: {', '.join(test_files)}")
 
        case [_, "test"]:
            print("Running all tests")
 
        case [_, unknown, *_]:
            print(f"Unknown command: {unknown}")
 
parse_args(["app", "init", "my-project"])  # Initializing project: my-project
parse_args(["app", "test", "a.py", "b.py"])  # Running tests: a.py, b.py
parse_args(["app", "build", "--release"])  # Building in release mode

3.10 以前の代替手段: match-case なしで switch-case を実現する

Python 3.9 以前を使っている場合や、単純な値の一致では match-case が大げさな場合は、次の代替手段が定番です。

if/elif/else チェーン

最も素直な方法です。

def handle_command(command):
    if command == "quit":
        print("Exiting program")
    elif command == "save":
        print("Saving file")
    elif command == "load":
        print("Loading file")
    elif command == "help":
        print("Showing help")
    else:
        print(f"Unknown command: {command}")

使う場面: 5〜6 個未満のケースで、ロジックが単純なとき。

辞書ディスパッチ

辞書を使って値を関数に割り当てます。

def cmd_quit():
    print("Exiting program")
 
def cmd_save():
    print("Saving file")
 
def cmd_help():
    print("Showing help")
 
commands = {
    "quit": cmd_quit,
    "save": cmd_save,
    "help": cmd_help,
}
 
def handle_command(command):
    action = commands.get(command)
    if action:
        action()
    else:
        print(f"Unknown command: {command}")

使う場面: 多数のケースを単純な処理に割り当てるとき。if/elif の O(n) に対し、O(1) の検索が可能です。

引数付きの辞書ディスパッチ

def calculate(operation, a, b):
    ops = {
        "+": lambda a, b: a + b,
        "-": lambda a, b: a - b,
        "*": lambda a, b: a * b,
        "/": lambda a, b: a / b if b != 0 else float("inf"),
    }
    func = ops.get(operation)
    if func is None:
        raise ValueError(f"Unknown operation: {operation}")
    return func(a, b)
 
print(calculate("+", 10, 3))  # 13
print(calculate("/", 10, 0))  # inf

Enum ベースのディスパッチ

型付きで自己説明的なコードに向いています。

from enum import Enum
 
class Direction(Enum):
    NORTH = "north"
    SOUTH = "south"
    EAST = "east"
    WEST = "west"
 
def move(direction: Direction, x: int, y: int) -> tuple[int, int]:
    offsets = {
        Direction.NORTH: (0, 1),
        Direction.SOUTH: (0, -1),
        Direction.EAST: (1, 0),
        Direction.WEST: (-1, 0),
    }
    dx, dy = offsets[direction]
    return (x + dx, y + dy)
 
print(move(Direction.NORTH, 0, 0))  # (0, 1)

比較: どの方法を使うべきか

アプローチPython バージョン最適用途複雑さ性能
match-case3.10+複雑なパターン、分解、型マッチ高い表現力if/elif と同程度
if/elif/elseすべて単純な値比較、< 6 ケースシンプルO(n) の線形探索
Dict dispatchすべて多数のケース、値→関数マッピング中程度O(1) で検索
Dict + lambdaすべて多数のケース、値→式マッピング中程度O(1) で検索
Enum dispatchすべて型付きの固定値、網羅的な分岐中程度O(1) で検索

目安:

  • 分解、型マッチ、複雑なパターンが必要なら match-case
  • 比較が単純で分岐が少ないなら if/elif
  • 単純な値→処理の対応が多いなら dict dispatch
  • 値の集合が固定で型を持たせたいなら enum dispatch

よくあるパターンとレシピ

型ベースのディスパッチ

def serialize(value):
    match value:
        case bool():  # Must come before int — bool is a subclass of int
            return "true" if value else "false"
        case int() | float():
            return str(value)
        case str():
            return f'"{value}"'
        case list():
            items = ", ".join(serialize(v) for v in value)
            return f"[{items}]"
        case dict():
            pairs = ", ".join(
                f'{serialize(k)}: {serialize(v)}' for k, v in value.items()
            )
            return "{" + pairs + "}"
        case None:
            return "null"
        case _:
            raise TypeError(f"Cannot serialize {type(value)}")
 
print(serialize({"name": "Alice", "scores": [95, 87, 92], "active": True}))
# {"name": "Alice", "scores": [95, 87, 92], "active": true}

重要: TrueFalse は Python では int のインスタンスでもあるため、int() より前に bool() を置いてください。

ネストしたパターンマッチング

def evaluate(expr):
    """Simple math expression evaluator."""
    match expr:
        case int(n) | float(n):
            return n
        case ("+", left, right):
            return evaluate(left) + evaluate(right)
        case ("-", left, right):
            return evaluate(left) - evaluate(right)
        case ("*", left, right):
            return evaluate(left) * evaluate(right)
        case ("/", left, right):
            divisor = evaluate(right)
            if divisor == 0:
                raise ZeroDivisionError("Division by zero")
            return evaluate(left) / divisor
        case ("neg", operand):
            return -evaluate(operand)
        case _:
            raise ValueError(f"Invalid expression: {expr}")
 
# (3 + 4) * 2
result = evaluate(("*", ("+", 3, 4), 2))
print(result)  # 14

match-case を使った状態機械

def tokenize(text):
    """Simple tokenizer using match-case as a state machine."""
    tokens = []
    i = 0
    while i < len(text):
        match text[i]:
            case ' ' | '\t' | '\n':
                i += 1  # Skip whitespace
            case '+' | '-' | '*' | '/':
                tokens.append(("OP", text[i]))
                i += 1
            case '(' | ')':
                tokens.append(("PAREN", text[i]))
                i += 1
            case c if c.isdigit():
                j = i
                while j < len(text) and text[j].isdigit():
                    j += 1
                tokens.append(("NUM", int(text[i:j])))
                i = j
            case c if c.isalpha():
                j = i
                while j < len(text) and text[j].isalnum():
                    j += 1
                tokens.append(("ID", text[i:j]))
                i = j
            case c:
                raise SyntaxError(f"Unexpected character: {c}")
    return tokens
 
print(tokenize("x + 42 * (y - 3)"))
# [('ID', 'x'), ('OP', '+'), ('NUM', 42), ('OP', '*'), ('PAREN', '('),
#  ('ID', 'y'), ('PAREN', ')')]

性能: match-case vs if/elif vs dict

単純な値の一致では、3 つの方法はどれも十分高速で、実用上の差はほとんどありません。10 個の文字列値に対するベンチマーク結果は次のとおりです。

import timeit
 
commands = ["cmd_0", "cmd_1", "cmd_2", "cmd_3", "cmd_4",
            "cmd_5", "cmd_6", "cmd_7", "cmd_8", "cmd_9"]
 
# if/elif approach
def if_elif_dispatch(cmd):
    if cmd == "cmd_0": return 0
    elif cmd == "cmd_1": return 1
    elif cmd == "cmd_2": return 2
    elif cmd == "cmd_3": return 3
    elif cmd == "cmd_4": return 4
    elif cmd == "cmd_5": return 5
    elif cmd == "cmd_6": return 6
    elif cmd == "cmd_7": return 7
    elif cmd == "cmd_8": return 8
    elif cmd == "cmd_9": return 9
    else: return -1
 
# dict dispatch approach
dispatch_dict = {f"cmd_{i}": i for i in range(10)}
def dict_dispatch(cmd):
    return dispatch_dict.get(cmd, -1)
 
# match-case approach
def match_dispatch(cmd):
    match cmd:
        case "cmd_0": return 0
        case "cmd_1": return 1
        case "cmd_2": return 2
        case "cmd_3": return 3
        case "cmd_4": return 4
        case "cmd_5": return 5
        case "cmd_6": return 6
        case "cmd_7": return 7
        case "cmd_8": return 8
        case "cmd_9": return 9
        case _: return -1
ApproachBest case (first match)Worst case (last match)Miss (no match)
if/elif~80ns~400ns~420ns
dict dispatch~60ns~60ns~65ns
match-case~90ns~450ns~470ns

要点: 辞書ディスパッチは位置に関係なく O(1) の定数時間です。if/elif と match-case はどちらも O(n) の線形探索です。20 ケース未満なら差はほぼ無視できます。性能ではなく可読性で選びましょう。

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

間違い 1: Python 3.10 より前で match-case を使う

# This causes SyntaxError in Python 3.9 and earlier
match command:  # SyntaxError: invalid syntax
    case "quit":
        pass

Python のバージョンを確認してください: python --version。3.9 以前なら if/elif または dict dispatch を使います。

間違い 2: 変数との比較を期待する

# WRONG — captures, does NOT compare
STATUS_OK = 200
match response_code:
    case STATUS_OK:  # This always matches and overwrites STATUS_OK!
        print("OK")
 
# RIGHT — use dotted name
class Status:
    OK = 200
    NOT_FOUND = 404
 
match response_code:
    case Status.OK:
        print("OK")
    case Status.NOT_FOUND:
        print("Not Found")
 
# RIGHT — use a guard
match response_code:
    case code if code == STATUS_OK:
        print("OK")

間違い 3: ワイルドカードのデフォルトを忘れる

# DANGEROUS — unmatched values silently do nothing
match command:
    case "save":
        save_file()
    case "load":
        load_file()
    # No default — "delete" silently falls through with no action
 
# SAFE — always include a default case
match command:
    case "save":
        save_file()
    case "load":
        load_file()
    case _:
        raise ValueError(f"Unknown command: {command}")

間違い 4: 重なり合うパターンの順序が गलत

# WRONG — the first case catches everything
match value:
    case x:            # Captures ANY value — always matches!
        print(f"Got: {x}")
    case 42:           # Never reached
        print("The answer")
 
# RIGHT — specific patterns first, general last
match value:
    case 42:
        print("The answer")
    case x:
        print(f"Got: {x}")

データサイエンスでの match-case の使い方

データ処理パイプラインでは、match-case は異なるデータ形式やクレンジング処理の分岐に役立ちます。

import csv
from pathlib import Path
 
def load_data(source):
    """Load data from different source types."""
    match source:
        case str() as path if path.endswith(".csv"):
            with open(path) as f:
                return list(csv.DictReader(f))
        case str() as path if path.endswith(".json"):
            import json
            with open(path) as f:
                return json.load(f)
        case list() as records:
            return records
        case dict() as single_record:
            return [single_record]
        case _:
            raise TypeError(f"Unsupported data source: {type(source)}")

インタラクティブにデータを探索するなら、PyGWalker (opens in a new tab) のようなツールを使うと、プロットコードを書かずに DataFrame を直接可視化できます。処理パイプラインが出力するデータを素早く確認したいときに便利です。

match-case を使った複雑な分岐を含むデータ処理スクリプトを作るなら、RunCell (opens in a new tab) は AI 搭載の Jupyter 環境を提供しており、実データを使ってパターンマッチングを対話的に試せます。

dataclass と named tuple での match-case

Python の match-case は dataclass や named tuple と特に相性がよいです。

from dataclasses import dataclass
from typing import Optional
 
@dataclass
class LogEntry:
    level: str
    message: str
    error: Optional[Exception] = None
 
def handle_log(entry: LogEntry):
    match entry:
        case LogEntry(level="CRITICAL", error=err) if err is not None:
            send_alert(f"CRITICAL with error: {err}")
            restart_service()
        case LogEntry(level="CRITICAL", message=msg):
            send_alert(f"CRITICAL: {msg}")
        case LogEntry(level="ERROR", error=err) if err is not None:
            log_to_file(f"ERROR: {err}")
        case LogEntry(level="WARNING" | "ERROR", message=msg):
            log_to_file(msg)
        case LogEntry(level="INFO" | "DEBUG"):
            pass  # Ignore low-priority logs

よくある質問

Does Python have a switch statement?

Python 3.10 以降には match-case 文があり、これは Python における switch-case に最も近いものです。構造的パターンマッチングをサポートしているため、従来の switch より強力です。オブジェクトの分解、パターン照合、変数の束縛ができます。Python 3.9 以前では、代替として if/elif/else の連鎖や辞書ディスパッチを使います。

What is the difference between match-case and switch-case?

Python の match-case にはフォールスルーがありません(break は不要です)。シーケンス、マッピング、クラスのパターンマッチングをサポートし、一致したデータから変数を分解・束縛できます。また、if 条件を使うガード節もサポートします。C や Java の従来の switch-case は、主にリテラル値の比較 בלבדです。

Is match-case faster than if/elif in Python?

単純な値の一致では、どちらもほぼ同等の速度で、線形に探索します。多数のケースがあるなら、辞書ディスパッチのほうが速い(O(1))です。20 ケース未満では性能差はほぼ無視できます。性能よりも可読性やパターンの複雑さで選びましょう。

Can I use match-case with Python 3.9?

いいえ。match-case 文は Python 3.10 以降が必要です。以前のバージョンでは if/elif/else の連鎖や辞書ディスパッチを使ってください。

Why does my match-case variable comparison not work?

case x: のような裸の名前は、既存の変数と比較するのではなく、任意の値を x にキャプチャします。変数と比較したい場合は、ガード節(case val if val == x:)かドット付きの名前(case MyClass.CONSTANT:)を使ってください。

What is the underscore _ in match-case?

_ のワイルドカードパターンは、値を変数に束縛せずに任意の値に一致します。switch 文の default: のようなデフォルトケースです。必ず最後に置いてください。

関連ガイド

📚