[Python] ‘match’ statement

Created
April 24, 2025
Created by
D
DaEun Kim
Tags
Python
Property

참고하기 좋은 레퍼런스

기본적인 용법은 아래와 같다

(맨 위에 있는 케이스부터 차례대로 검사한다)

def match_tuple(t: tuple, flag: bool = False):
    match t:
        case ("JADU", 1):
            print("case 1")
        case ("JADU", 2) if flag:
            print("case 2 with flag=true")
        case ("JADU", 2):
            print("case 2 without flag")
        case ("JADU", n):
            print(f"case 3 with {n=}")
        case _:
            print("nothing matched")
match_tuple(("JADU", 1))                # case 1
match_tuple(("JADU", 2))                # case 2 without flag
match_tuple(("JADU", 2), True)          # case 2 with flag=true
match_tuple(("JADU", 100))              # case 3 with n=100
match_tuple(("DAEUN", 2))               # nothing matched

케이스 구문에는 단순한 리터럴 비교 뿐만 아니라 다양한 패턴을 작성할 수 있다

  • 매칭의 대상에 변수 바인딩 가능
  • 매칭의 대상이 시퀸스 객체일 경우 * (positional argument) 으로 시퀸스 내 각 요소 접근 가능
  • 매칭의 대상이 딕셔너리 객체일 경우 ** (keyword argument) 으로 각 key/value 접근 가능
  • 매칭의 대상이 사용자가 정의한 클래스 객체일 경우 클래스 syntax 사용 가능
  • | (vertical bar) 으로 2개 이상의 OR 조건 생성 가능
  • as 으로 서브 패턴 접근 가능
  • if 구문으로 guard (조건문 추가) 가능

상속 관계에 있는 클래스 객체를 비교하는 경우

from dataclasses import dataclass


@dataclass
class Point:
    x: float
    y: float

@dataclass
class Shape:
    center: Point

@dataclass
class Circle(Shape):
    radius: float


@dataclass
class Rectangle(Shape):
    width: float
    height: float


@dataclass
class Line:
    center: Point


def match_shape(shape: Shape):
    match shape:
        case Shape(center=Point(0.0, 0.0)):
            print("shape with zero point")
        case Circle(center=Point(x=x, y=y), radius=r):
            print(f"circle with {x=}, {y=}, {r=}")
        case Rectangle(center=Point(x=x, y=y), width=w, height=h):
            print(f"rectangle with {x=}, {y=}, {w=}, {h=}")
        case _:
            print("nothing matched")
match_shape(Circle(center=Point(0.0,0.0), radius=20.0))                  # shape with zero point
match_shape(Rectangle(center=Point(0.0,0.0), width=10.0, height=10.0))   # shape with zero point

match_shape(Circle(center=Point(1.0, 1.0), radius=35.0))                 # circle with x=1.0, y=1.0, r=35.0
match_shape(Rectangle(center=Point(1.0 ,1.0), width=35.0, height=35.0))  # rectangle with x=1.0, y=1.0, w=35.0, h=35.0

match_shape(Line(center=Point(0.0, 0.0)))                                # nothing matched

if-elif-else 으로 작성한다면 대략 아래와 같이 작성해야 할텐데,

match 를 사용하니 훨씬 간결하고 명확해진다

def match_shape(shape: Shape):
  if isinstance(shape, Shape):
    if shape.center.x == 0.0 and shape.center.y == 0.0:
        print("shape with zero point")
        return
    if isinstance(shape, Circle):
        print(f"circle with x={shape.x}, y={shape.y}, r={shape.radius}")
    else:
        print(f"rectangle with x={shape.x}, y={shape.y}, width={shape.width}, height={shape.height}")}
  else:
    print("nothing matched")

케이스 구문에서 패턴 매칭하느라 분해된 개별 요소는 as 를 통해 원본으로 접근할 수 있다

from dataclasses import dataclass


@dataclass
class Point:
    x: float
    y: float

@dataclass
class Shape:
    name: str
    center: Point

@dataclass
class Circle(Shape):
    radius: float


@dataclass
class Rectangle(Shape):
    width: float
    height: float


def match_shape(shape: Shape):
    match shape:
        case Shape(center=Point(0, 0)) as shape:
            print(f"{shape.__class__.__name__} with zero point")
        case Circle(center=Point(x=x, y=y) as center) as circle:
            print(f"circle with {center=}, name={circle.name}")
        case Rectangle(center=Point(x=x, y=y) as center) as rectangle:
            print(f"rectangle with {center=}, name={rectangle.name}")
        case _:
            print("nothing matched")
match_shape(Circle(center=Point(0.0,0.0), radius=20.0, name="c1"))                  # Circle with zero point
match_shape(Rectangle(center=Point(0.0,0.0), width=10.0, height=10.0, name="r1"))   # Rectangle with zero point

match_shape(Circle(center=Point(1.0, 1.0), radius=35.0, name="c2"))                 # circle with center=Point(x=1.0, y=1.0), name=c2
match_shape(Rectangle(center=Point(1.0 ,1.0), width=35.0, height=35.0, name="r2"))  # rectangle with center=Point(x=1.0, y=1.0), name=r2

각 케이스 블록에서 1개 이상의 조건을 만들기 위해 아래와 같이 vertical bar 활용하는 경우

from http import HTTPStatus

def classify_http_status(status):
    match status:
        case HTTPStatus.OK | HTTPStatus.CREATED | HTTPStatus.NO_CONTENT:
            return "성공 응답"
        case HTTPStatus.MOVED_PERMANENTLY | HTTPStatus.FOUND | HTTPStatus.TEMPORARY_REDIRECT | HTTPStatus.PERMANENT_REDIRECT:
            return "리다이렉션 응답"
        case HTTPStatus.BAD_REQUEST | HTTPStatus.UNAUTHORIZED | HTTPStatus.FORBIDDEN | HTTPStatus.NOT_FOUND:
            return "클라이언트 오류 응답" 
        case HTTPStatus.INTERNAL_SERVER_ERROR | HTTPStatus.BAD_GATEWAY | HTTPStatus.SERVICE_UNAVAILABLE | HTTPStatus.GATEWAY_TIMEOUT:
            return "서버 오류 응답"
        case n if 100 <= n < 600:
            return "기타 HTTP 상태 코드"
        case _:
            return "잘못된 HTTP 상태 코드"
classify_http_status(200) # 성공 응답
classify_http_status(401) # 클라이언트 오류 응답
classify_http_status(503) # 서버 오류 응답
classify_http_status(512) # 기타 HTTP 상태 코드
classify_http_status(777) # 잘못된 HTTP 상태 코드

if-elif-else 으로 구현할 경우 다소 복잡해지는 코드를 match-case 으로 더 간결하게 작성 가능함

def match_cmd(cmd: str):
    match cmd.split():
        case ["exit" | "bye" | "quit"]:
            print("program terminated")
        case ["open" | "load", file]:
            print(f"{file} opened")
        case ["search" | "find", *terms] if len(terms) > 0:
            print(f"search '{" ".join(terms)}'")
        case _:
            print("unknown command")
match_cmd("quit")            # program terminated
match_cmd("exit")            # program terminated

match_cmd("open aaa")        # aaa opened
match_cmd("load bbb")        # bbb opened

match_cmd("search cat")      # search 'cat'
match_cmd("search OIIA cat") # search 'OIIA cat'

match_cmd("hello")           # unknown command