[Python] match문: 구조적 패턴 매칭
정의
match 문(3.10+, PEP 634)은 구조적 패턴 매칭. C의 switch보다 훨씬 강력해 타입·구조·내용을 동시에 검사하고 동시에 binding한다. Haskell, Rust, Scala 등의 ADT 매칭에서 영향.
기본
def http_status(code):
match code:
case 200:
return "OK"
case 301 | 302 | 307 | 308:
return "Redirect"
case 404:
return "Not Found"
case n if 500 <= n < 600:
return f"Server Error {n}"
case _:
return "Unknown"
print(http_status(200)) # OK
print(http_status(503)) # Server Error 503
|로 OR 패턴, if로 가드(추가 조건), _은 와일드카드 (어떤 값이든 매치, 변수 바인딩 X).
패턴 종류
리터럴 패턴
match value:
case 0: ...
case "ok": ...
case True: ...
case None: ...
== 비교 (단, None, True, False는 is 사용).
캡처 패턴
match point:
case (x, y): # 변수 바인딩
print(x, y)
이름 단독은 항상 매치 + 바인딩. 리터럴과 구분하려면 점 표기:
ORIGIN = 0
match value:
case ORIGIN: # 캡처! ORIGIN을 value로 덮어씀
...
case obj.ORIGIN: # 리터럴 비교 (.은 점 attribute lookup)
...
이름이 단순 식별자면 캡처, MODULE.NAME 같은 경로면 값 비교.
OR 패턴
match cmd:
case "quit" | "exit" | "q":
sys.exit()
가드 (if)
match point:
case (x, y) if x == y:
print("대각선")
case (x, y):
print("일반")
시퀀스 패턴
match items:
case []:
print("빈")
case [x]:
print(f"하나: {x}")
case [x, y]:
print(f"둘: {x}, {y}")
case [x, *rest]:
print(f"앞: {x}, 나머지: {rest}")
case [*init, last]:
print(f"마지막: {last}, 앞: {init}")
case [_, _, _]:
print("정확히 3개")
list, tuple 모두 매치 (str과 bytes는 의도적 제외, 문자열 매치는 리터럴로).
매핑 패턴
match event:
case {"type": "click", "x": x, "y": y}:
click_at(x, y)
case {"type": "key", "code": 27}:
handle_escape()
case {"type": "key", **rest}:
handle_key(**rest)
**rest로 나머지 키 캡처. 키 일부만 매치 (다른 키는 무시, 빌트인 dict 매치는 부분 일치).
클래스 패턴
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
match p:
case Point(x=0, y=0):
print("원점")
case Point(x=0, y=y):
print(f"y축, y={y}")
case Point(x=x, y=y) if x == y:
print("대각선")
case Point(x=x, y=y):
print(f"({x}, {y})")
isinstance(p, Point) + 속성 검사 + 바인딩 동시.
match_args: 위치 매칭
class Color:
__match_args__ = ("r", "g", "b")
def __init__(self, r, g, b):
self.r, self.g, self.b = r, g, b
match color:
case Color(0, 0, 0):
print("검정")
case Color(255, 255, 255):
print("흰색")
case Color(r, g, b) if r == g == b:
print(f"회색 {r}")
@dataclass는 자동으로 __match_args__ 생성.
as 패턴 (별칭)
match shape:
case Point(x=0, y=y) as p:
process(p, y)
서브패턴 매치 + 전체를 변수에 저장.
빌트인 캡처
match value:
case int():
print("int")
case str():
print("str")
case list() | tuple():
print("sequence")
isinstance 패턴. 인수가 없으면 타입만 검사.
실제 예제
토큰 처리 (인터프리터)
def evaluate(node):
match node:
case {"op": "+", "left": l, "right": r}:
return evaluate(l) + evaluate(r)
case {"op": "*", "left": l, "right": r}:
return evaluate(l) * evaluate(r)
case {"value": v}:
return v
case _:
raise ValueError(f"Unknown: {node}")
HTTP 라우터
def route(request):
match request:
case {"method": "GET", "path": "/"}:
return index()
case {"method": "GET", "path": f"/users/{user_id}"}:
# f-string은 매치 안 됨, 위는 실제로는 안 동작
...
case {"method": "POST", "path": "/login", "body": {"username": u, "password": p}}:
return login(u, p)
case _:
return not_found()
(경로 패턴 매칭은 별도 라이브러리: starlette router 등)
이벤트 핸들러
def handle(event):
match event:
case ("connect", host, port):
connect(host, port)
case ("disconnect",):
disconnect()
case ("send", payload) if isinstance(payload, bytes):
send(payload)
case _:
log_unknown(event)
Optional 처리
def safe_get(d, *keys):
match d:
case dict() if all(k in d for k in keys):
return tuple(d[k] for k in keys)
case _:
return None
매치 순서 / fall-through 없음
위에서 아래로 첫 매치만 실행. C/Java처럼 break 필요 없음. _ 또는 가장 일반적인 패턴은 맨 마지막에.
match x:
case _: # 항상 매치
print("first!")
case 1: # 도달 불가
...
함정
1. 캡처 vs 리터럴 비교
ORIGIN = (0, 0)
match point:
case ORIGIN: # 함정! ORIGIN을 point로 덮어씀
...
# 의도가 (0, 0)과 비교라면
match point:
case (0, 0):
...
# 또는 enum/상수
class Const:
ORIGIN = (0, 0)
match point:
case Const.ORIGIN: # 점이 있으면 리터럴 비교
...
2. str/bytes는 시퀀스 매치 X
match "hello":
case [c, *rest]: # 매치 안 됨
...
case "hello": # 리터럴은 OK
...
str의 한 글자씩 매치는 의도적으로 제외 (혼란 방지).
3. dict 매치는 부분 매치
match {"a": 1, "b": 2, "c": 3}:
case {"a": 1}: # 매치! ("a"만 검사, 나머지 무시)
print("matched")
정확히 키 셋이 일치하길 원하면 **rest와 길이 검사.
4. type[X]로 매칭 (3.10에선 안 됨)
match value:
case int(): # OK: 인스턴스 매치
...
case type[int](): # X
...
타입 자체 매치엔 isinstance 또는 if.
5. 가드 안에서 캡처 변수 사용 가능
match point:
case (x, y) if x > y: # OK: 가드 안에서 x, y 사용
...
가드는 매치 직후 평가. 캡처 변수 활용 가능.
match vs if/elif 체인
| 상황 | 선택 |
|---|---|
| 값 비교 (간단) | if/elif (3.9 이하도 동작) |
| 구조 분해 + 바인딩 | match |
| 타입 디스패치 | match (또는 singledispatch) |
| 복잡한 dict/tuple/class 매치 | match |
| 한두 가지 분기 | if |
match가 더 빠른 경우 많음 (점프 테이블 컴파일).
한계
- 정규식 매치 X (직접 if + re.match)
- 깊은 OR 안에서 캡처 변수 일관성 강제 (
case [x] | (x,):OK) - catch-all 없으면 매치 안 될 수도 (의도적 동작)
- 3.10+ 만 사용 가능
💬 댓글