Python의 match/case 문을 실전에서 활용하는 방법을 다룹니다. 시퀀스, 매핑, 클래스 패턴부터 가드 조건, 중첩 패턴까지 실무 코드로 익힙니다.
Python 3.10에서 도입된 구조적 패턴 매칭(Structural Pattern Matching)은 단순한 switch-case가 아닙니다. 데이터의 구조를 검사하고, 동시에 내부 값을 추출하는 강력한 제어 흐름 도구입니다.
기존의 if-elif 체인은 조건이 복잡해질수록 가독성이 급격히 떨어집니다. 특히 JSON API 응답, 이벤트 처리, 명령어 파싱처럼 데이터의 형태에 따라 분기하는 코드에서 이 문제가 두드러집니다.
def handle_event(event: dict) -> str:
if event.get("type") == "click" and "x" in event and "y" in event:
x = event["x"]
y = event["y"]
return "Click at " + str(x) + ", " + str(y)
elif event.get("type") == "keypress" and "key" in event:
key = event["key"]
if event.get("modifier"):
return event["modifier"] + "+" + key
return key
elif event.get("type") == "scroll":
return "Scroll " + str(event.get("direction", "down"))
else:
return "Unknown event"패턴 매칭을 사용하면 동일한 로직을 더 명확하게 표현할 수 있습니다.
match 문은 주어진 값(subject)을 여러 패턴과 순서대로 비교합니다. 첫 번째로 매칭되는 패턴의 블록이 실행됩니다.
가장 단순한 형태는 리터럴 값과의 비교입니다.
def http_status(code: int) -> str:
match code:
case 200:
return "OK"
case 301:
return "Moved Permanently"
case 404:
return "Not Found"
case 500:
return "Internal Server Error"
case _:
return "Unknown Status: " + str(code)_는 와일드카드 패턴으로, 모든 값과 매칭됩니다. 다른 언어의 default와 같은 역할을 합니다.
여러 패턴을 파이프(|)로 결합할 수 있습니다.
def classify_status(code: int) -> str:
match code:
case 200 | 201 | 202 | 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 "Other"리스트나 튜플의 구조를 매칭하고 내부 값을 동시에 추출할 수 있습니다. 이것이 패턴 매칭의 진정한 힘입니다.
def parse_command(command: list[str]) -> str:
match command:
case ["quit"]:
return "Exiting"
case ["hello", name]:
return "Hello, " + name
case ["move", direction, distance]:
return "Moving " + direction + " by " + distance
case ["move", direction]:
return "Moving " + direction + " by 1"
case ["drop", *items]:
return "Dropping: " + ", ".join(items)
case []:
return "Empty command"
case _:
return "Unknown command"
# 사용 예시
parse_command(["hello", "Alice"]) # "Hello, Alice"
parse_command(["move", "north"]) # "Moving north by 1"
parse_command(["drop", "a", "b"]) # "Dropping: a, b"*items는 나머지 요소를 모두 캡처하는 확장 언패킹(extended unpacking)입니다. 가변 길이 시퀀스를 처리할 때 유용합니다.
딕셔너리의 구조를 매칭합니다. API 응답이나 설정 파일 처리에서 자주 사용됩니다.
def handle_api_response(response: dict) -> str:
match response:
case {"status": "success", "data": data}:
return "Data received: " + str(len(data)) + " items"
case {"status": "error", "message": msg, "code": code}:
return "Error " + str(code) + ": " + msg
case {"status": "error", "message": msg}:
return "Error: " + msg
case {"status": "pending", "retry_after": seconds}:
return "Retry after " + str(seconds) + " seconds"
case _:
return "Unexpected response format"매핑 패턴은 부분 매칭(partial matching)을 수행합니다. 패턴에 명시한 키가 존재하면 매칭되며, 딕셔너리에 추가 키가 있어도 무시됩니다. 모든 키를 캡처하려면 **rest 구문을 사용합니다.
def process_config(config: dict) -> None:
match config:
case {"host": host, "port": int(port), **rest}:
print("Server: " + host + ":" + str(port))
if rest:
print("Additional config: " + str(rest))클래스 인스턴스의 속성을 기준으로 매칭할 수 있습니다. 도메인 모델이나 이벤트 처리에서 특히 유용합니다.
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: Circle | Rectangle) -> str:
match shape:
case Circle(center=Point(x=0, y=0), radius=r):
return "Origin circle with radius " + str(r)
case Circle(center=Point(x=x, y=y), radius=r) if r > 100:
return "Large circle at (" + str(x) + ", " + str(y) + ")"
case Circle(center=c, radius=r):
return "Circle at " + str(c) + " with radius " + str(r)
case Rectangle(width=w, height=h) if w == h:
return "Square with side " + str(w)
case Rectangle(top_left=Point(x=x, y=y), width=w, height=h):
return "Rectangle at (" + str(x) + ", " + str(y) + ") size " + str(w) + "x" + str(h)dataclass는 자동으로 __match_args__를 설정하므로 위치 기반 매칭이 가능합니다. 일반 클래스에서도 이 속성을 정의할 수 있습니다.
class Command:
__match_args__ = ("action", "target")
def __init__(self, action: str, target: str) -> None:
self.action = action
self.target = target
def execute(cmd: Command) -> str:
match cmd:
case Command("create", target):
return "Creating " + target
case Command("delete", target):
return "Deleting " + target
case Command("update", target):
return "Updating " + targetif 절을 추가하여 패턴 매칭에 조건을 부여할 수 있습니다. 패턴이 구조적으로 매칭된 후에 가드 조건이 평가됩니다.
def classify_age(person: dict) -> str:
match person:
case {"name": name, "age": age} if age < 0:
return name + ": invalid age"
case {"name": name, "age": age} if age < 13:
return name + ": child"
case {"name": name, "age": age} if age < 20:
return name + ": teenager"
case {"name": name, "age": age} if age < 65:
return name + ": adult"
case {"name": name, "age": age}:
return name + ": senior"
case _:
return "Unknown person"실무에서 패턴 매칭이 가장 빛나는 영역은 이벤트 처리입니다. 다양한 형태의 이벤트를 타입에 따라 분기하고, 필요한 데이터를 추출하는 과정이 자연스럽습니다.
from dataclasses import dataclass, field
from datetime import datetime
@dataclass
class UIEvent:
timestamp: datetime = field(default_factory=datetime.now)
@dataclass
class Click(UIEvent):
x: int = 0
y: int = 0
button: str = "left"
@dataclass
class KeyPress(UIEvent):
key: str = ""
modifiers: list[str] = field(default_factory=list)
@dataclass
class Scroll(UIEvent):
dx: int = 0
dy: int = 0
@dataclass
class Resize(UIEvent):
width: int = 0
height: int = 0
def handle_ui_event(event: UIEvent) -> str:
match event:
case Click(x=x, y=y, button="right"):
return "Context menu at " + str(x) + ", " + str(y)
case Click(x=x, y=y, button="left"):
return "Click at " + str(x) + ", " + str(y)
case KeyPress(key="c", modifiers=["ctrl"]):
return "Copy"
case KeyPress(key="v", modifiers=["ctrl"]):
return "Paste"
case KeyPress(key=k, modifiers=[]):
return "Key: " + k
case KeyPress(key=k, modifiers=mods):
return "+".join(mods) + "+" + k
case Scroll(dy=dy) if dy > 0:
return "Scroll up"
case Scroll(dy=dy) if dy < 0:
return "Scroll down"
case Resize(width=w, height=h):
return "Resize to " + str(w) + "x" + str(h)
case _:
return "Unhandled event"REST API의 요청을 패턴 매칭으로 라우팅하는 예시입니다.
def route_request(method: str, path: list[str], body: dict | None = None) -> dict:
match (method, path):
case ("GET", ["users"]):
return {"action": "list_users"}
case ("GET", ["users", user_id]):
return {"action": "get_user", "id": user_id}
case ("POST", ["users"]) if body:
return {"action": "create_user", "data": body}
case ("PUT", ["users", user_id]) if body:
return {"action": "update_user", "id": user_id, "data": body}
case ("DELETE", ["users", user_id]):
return {"action": "delete_user", "id": user_id}
case ("GET", ["users", user_id, "posts"]):
return {"action": "list_user_posts", "user_id": user_id}
case _:
return {"action": "not_found", "method": method, "path": "/".join(path)}튜플 매칭을 활용하면 HTTP 메서드와 경로를 동시에 검사할 수 있습니다.
Python 3.12의 타입 파라미터 문법과 함께 사용하면, 타입 안전한 패턴 매칭 코드를 작성할 수 있습니다.
from dataclasses import dataclass
@dataclass
class Success[T]:
value: T
@dataclass
class Failure:
error: str
code: int = 0
type Result[T] = Success[T] | Failure
def process_result[T](result: Result[T]) -> str:
match result:
case Success(value=v):
return "Success: " + str(v)
case Failure(error=e, code=0):
return "Error: " + e
case Failure(error=e, code=c):
return "Error " + str(c) + ": " + e패턴은 위에서 아래로 순서대로 평가됩니다. 더 구체적인 패턴을 먼저 배치해야 합니다.
# 잘못된 순서: 첫 번째 case가 모든 것을 매칭
match point:
case Point(x=x, y=y): # 항상 매칭됨
...
case Point(x=0, y=0): # 도달 불가
...
# 올바른 순서: 구체적인 패턴을 먼저
match point:
case Point(x=0, y=0): # 원점만 매칭
...
case Point(x=x, y=y): # 나머지 모든 Point
...패턴 매칭은 if-elif 체인과 비슷한 성능을 보입니다. 내부적으로 최적화된 점프 테이블을 사용하지 않으므로, 수백 개의 case를 나열하는 것은 권장하지 않습니다. 그러한 경우에는 딕셔너리 기반 디스패치가 더 적합합니다.
패턴 매칭은 데이터의 구조를 검사하고 분해해야 할 때 가장 효과적입니다. 단순한 값 비교에는 if-elif가 더 명확할 수 있습니다. 중첩된 데이터 구조, 다형적 타입 처리, 이벤트 디스패치에서 패턴 매칭의 진가가 드러납니다.
구조적 패턴 매칭은 Python에서 복잡한 조건 분기를 선언적으로 표현하는 도구입니다.
__match_args__를 통해 커스텀 클래스도 위치 기반 매칭을 지원합니다4장에서는 Python 3.12의 유연한 f-string 파싱(PEP 701)과 3.12~3.13에 걸쳐 지속적으로 개선된 에러 메시지를 다룹니다. 디버깅 경험을 크게 향상시키는 이 변화들의 실체를 살펴봅니다.
이 글이 도움이 되셨나요?
Python 3.12의 PEP 701 유연한 f-string 파싱과 3.12~3.13의 에러 메시지 개선을 다룹니다. 컬러 트레이스백, 제안 기반 에러 등 디버깅 경험 향상을 살펴봅니다.
Python 3.12에서 도입된 PEP 695 타입 파라미터 문법을 상세히 분석합니다. TypeVar의 간결한 선언, 제네릭 클래스와 함수의 새 문법, type 별칭을 다룹니다.
Python 3.13에서 도입된 PyREPL의 구문 강조, 멀티라인 편집, 자동완성, 히스토리 관리 등 현대적 REPL 기능을 실전 예시와 함께 다룹니다.