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