파이썬 패턴매칭

파이썬 3.10에서는 구조적 패턴 매칭이라는 새로운 문법이 도입된다. 패턴매칭은 match 문이라는 새로운 구문에 적용된며, 스칼라 같은 다른 언어들에서 영향을 받았다고 한다. match 문은 어떤 값(subject)을 여러 다른 형태(pattern)들과 들어맞을 때 까지 비교한다. 이는 C와 같은 언어의 switch 문과 같은 것이 아니냐고 할 수 있겠지만, switch 문이 여러 케이스의 값을 비교하는 것과 달리, match 문은 값에 대해서 패턴에 들어맞는지를 보는 것이 차이라 하겠다.

파이썬 match 문에서 주어진 값은 다음과 같은 패턴 중 하나를 case 절에 사용하는 것으로 체크할 수 있게 된다.

  • 임의의 단일 값 : C의 switch-case와 같이 주어진 값이 해당 값에 일치하는지를 체크한다.
  • 튜플이나 리스트 : 언패킹이 가능한 연속열이 패턴으로 제시되면, 주어진 값을 언패킹하여 각각의 요소들을 비교한다. 패턴 내 요소가 값이면 일치하는지 검사하고, 변수명이라면 요소 값을 바인딩한다.
  • 키-값 매핑 : 주어진 객체가 패턴으로 제시된 사전과 동일한 키가 있는지, 그리고 그 때의 값은 일치하는지를 검사한다. 값 자리에 변수명이 왔다면 바인딩한다.
  • 어떤 클래스와 그 속성 (주로 @dataclass를 통해서 정의된 클래스)
  • 와일드 카드

다음과 같은 것들이 실제 패턴으로 사용될 수 있는 형태의 예이다.

  • [first, second, *rest]
  • Point2d(x, 0)
  • {"name": "Bruce", "age": age}
  • 42

조금 더 자세한 예를 살펴보자. 다음 예는 좌표를 표현할 수 있는 여러 값을 받아서 3차원 좌표 객체를 생성하는 예를 표현하는 것이다.

def make_point_3d(pt):
    match pt:
        case (x, y):  # pt가 2개짜리 튜플이라면 값을 x, y에 바인딩한다.
            return Point3d(x, y, 0)
        case (x, y, z):  # pt가 3개짜리 튜플이라면 값을 x, y, z에 바인딩한다.
            return Point3d(*pt)
        case Point2d(x, y):  # Point2d 인스턴스라면
            # __init__()의 인자 두 개를 x, y에 매칭한다. 
            return Point3d(x, y, 0)
        case Point3d(_, _, _):  # Point3d 인스턴스이기만 하다면, 속성값은 상관없다.
            return pt
        case _:
            raise TypeError("not a point we support")

또 다른 예를 살펴보자. 사용자로부터 문자열로 된 명령을 처리해야 하는 케이스를 가정해보자. command는 사용자가 입력하는 명령으로 “quit”, “look” 과 같이 한 단어로 된 명령이거나, 두 단어로 된 “get paper”, “go west” 같은 패턴일 수도 있다. 다음 코드는 패턴에 따라서 작동하는 분기를 보여준다. 만약 if 문만으로 하려했다면 상당히 귀찮고 긴 코드가 될 수 있을 것이나, 패턴 매칭으로는 아주 간단하게 분기를 처리할 수 있다.

match command.split():
    case ["quit"]:
        print("Goodbye!")
        quit_game()
    case ["look"]:
        current_room.describe()
    case ["get", obj]:
        character.get(obj, current_room)
    case ["go", direction]:
        current_room = current_room.neighbor(direction)
    case _:
        pass

OR 매칭 및 서브 패턴

패턴에는 OR 매칭이 가능하다. | 를 사이에 두고 패턴을 구분하여, 나열된 패턴 중 하나에 매치하도록 할 수 있다. 앞선 예에서 “go north”와 “north”를 같은 명령으로 처리하려면 다음과 같이 할 수 있을 것이다.

match command.split():
    case ["north"] | ["go", "north"]:
        current_room = current_room.neighbor("north")
    # ....

|는 패턴 사이 뿐만아니라, 패턴 내의 서브 패턴을 구분하는데에도 사용될 수 있다. 4개의 방향에 대해서 “go” 명령을 쓰려면 다음과 같이 패턴을 정의해도 된다.

match command.split():
    case ["go", ("north" | "south" | "east" | "west")]:
        # ....

그런데 위 패턴에서 문제는 방향 값을 매칭을 했지만, 그 아래 코드 블럭에서 어떤 방향이 매치되었는지를 알 수 없다는 문제가 있다. as를 사용해서 서브 패턴을 별도의 변수에 바인딩하면 매칭된 값을 변수로 사용할 수 있게 된다.

match command.split():
    case ["go", ("north" | "south" | "east" | "west") as direction]:
        current_room = current_room.neighbor(direction)

가드

패턴이 매치됐더라도, 특정한 요소의 허용 범위 같은 게 있을 수 있다. 이 경우에는 if 절을 추가하여 추가로 검사할 수 있다. 가드절이 있는 경우에는 가드의 조건까지 통과해야 매치가 완료된 것으로 본다. 아래는 위의 예와 같은 것인데, avaiable_direction 이라는 별도의 리스트에 가능한 방향들을 정의해두고, 유효한 방향 값이 들어왔을 때에만 작동하도록 할 수 있다.

match command.split():
    case ["go", direction] if direction in\
                              current_room.available_direction:
        current_room = current_room.neighbor(direction)

정리

기본적으로 값, 클래스, 연속열, 맵핑의 케이스로 구분되는 패턴들은 다시 패턴속에 패턴이나, as 바인딩, if절에 의한 가드 등으로 얼마든지 확장할 수 있기 때문에 잘 활용한다면 매우 편하게 쓸 수 있을 것 같다.

3.10의 베타가 나온 지금도 (파이썬은 베타에 접어들면 기능에 대한 목록은 확정됐다고 본다) 패턴 매칭에 대한 찬반 논의는 많이 있다. 개인적으로는 다른 함수형 언어에서 볼 수 있던 패턴 매칭 기능이 파이썬에서 적용된다고 해서 매우 반기는 입장이다.

Read more

워드프레스에서 고스트로 이전

워드프레스에서 고스트로 이전

이 글을 쓰면서도 믿기 힘든 사실인데, 블로그라는 걸 처음 시작한지가 20년이 되었습니다. 이글루스에서 처음 시작했다가, SK컴즈가 인수한다고 발표함과 동시에 워드프레스로 플랫폼을 옮겼죠. 워드프레스오 옮긴 이후에는 호스팅 환경을 이리 저리 옮기긴 했지만 거의 18년 가까이 워드프레스를 사용해온 것 같습니다. 그 동안 워드프레스는 블로깅 툴에서 명실상부한 범용CMS로 발전했습니다. 사실 웬만한 홈페이지들은 이제

By sooop
띄어쓰기에 대한 생각

띄어쓰기에 대한 생각

업무 메일을 쓸 때 가장 많이 쓰는 말 중에 하나가 메일 말미에 ‘업무에 참고 부탁 드립니다.‘인데요, 어느 날부터 아웃룩에서 이 ‘부탁 드립니다’가 틀렸다고 맞춤법 지적을 하기 시작했습니다. 맞는 말은 ‘부탁드립니다’라고 붙여 쓰는 거라고. 사실 아래아한글 시절부터 이전의 MS워드까지, 워드프로세서들의 한국어 맞춤법 검사 실력은 거의 있으나 마나 한

By sooop

구글 포토에서 아이클라우드로 탈출한 후기

한 때 구글 포토가 백업 용량을 무제한으로 제공해 주겠다고해서, 구글 포토를 사용해서 사진을 백업해왔습니다. 물론 이 이야기의 결말은 저나 이 글을 읽고 있는 여러분이나 모두 알고 있습니다. 사실 AI에게 학습 시킬 이미지 데이터를 모으기 위한 것일 뿐이라거나 하는 이야기는 그 당시에도 있었습니다만, 에이 그래도 구글인데 용량은 넉넉하게 주겠지…하는 순진한

By sooop

Julia의 함수 사용팁

연산자의 함수적 표기 Julia의 연산자는 기본적으로 함수이며, 함수 호출 표기와 같은 방식으로 호출하는 것이 가능합니다. 또한 그 자체로 함수이기 때문에 filter(), map() 과 같이 함수를 인자로 받는 함수에도 연산자를 그대로 적용하는 것이 가능합니다. 특히 + 연산자는 sum() 함수와 같이 여러 인자를 받아 인자들의 합을 구할 수 있습니다. 2 + 3 # = 5 +(2,

By sooop