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 年以上にわたる実際の制約でした。主要な他言語には switch や case 構文がありましたが、Python は if/elif の連鎖と辞書ディスパッチに完全に依存していました。Guido van Rossum は switch-case の提案を何度も退け、if/elif で十分だと主張していました。
その状況は Python 3.10 で PEP 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: hellomatch キーワードは対象式を 1 回だけ評価します。各 case はパターンを定義します。Python は上から順にパターンを試し、最初に一致したものを実行します。ワイルドカード _ はデフォルトケースで、あらゆる値に一致します。
match-case と switch-case の主な違い
C、Java、JavaScript、Go に慣れているなら、重要な違いは次のとおりです。
| 機能 | Python match-case | C/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 Found2. 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 47. ガード節
任意のパターンに 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 mode3.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)) # infEnum ベースのディスパッチ
型付きで自己説明的なコードに向いています。
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-case | 3.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}重要: True と False は 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) # 14match-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| Approach | Best 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":
passPython のバージョンを確認してください: 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: のようなデフォルトケースです。必ず最後に置いてください。
関連ガイド
- Python Try Except: Error Handling Guide
- Python Type Hints: Complete Tutorial
- Python Decorators Explained
- Python Collections Module Guide
- Python Enumerate Function
- Python Assert Statement