본문으로 건너뛰기
김신건의 로그

[Python] match문: 구조적 패턴 매칭

· 수정 · 📖 약 2분 · 642자/단어 #python #match #pattern-matching #advanced
python match, structural pattern matching, PEP 634, case patterns, match-case

정의

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, Falseis 사용).

캡처 패턴

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+ 만 사용 가능

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기