논란도 많고 말도 많았던, switch-case의 파이썬 버젼입니다. 이게 드디어,파이썬 3.10 부터 지원을 합니다. 대신에 오랫동안 검토가 된 만큼 몇가지 일반적인 switch-case문과는 다른 점들이 있습니다.
기본적인 형식은 아래와 같습니다.
def match_test(cond):
match cond:
case 'AAA':
print ('AAA')
case 'BBB':
print ('BBB')
case 'CCC':
print ('CCC')
case _:
print ('others')
여기서 case _는 다른 언어들의 default와 동일 의미합니다. 아무것도 매치되지 않을 경우 case _의 구문을 수행합니다.
패턴 비교 - 리터럴
기본적인것은 switch와 같이 리터럴(패턴이라고 함)에 하나하나 비교하는 것입니다. 아래 예에서 status가 대상subject이며, 각 case문의 400, 404, 418이 패턴입니다. 401 | 402 | 403 처럼 or 연산은 ‘|’를 이용해서 할 수 있습니다.
def http_error(status):
match status:
case 400:
return "Bad request"
case 401 | 402 | 403: # 401 or 402 or 403
return "Permission Denied"
case 404:
return "Not found"
case 418:
return "I'm a teapot"
case _:
return "Something's wrong with the internet"
http_error() 함수는 인자로 받은 status가 각각 400, 404, 418일 때 해당 문자열을 내줍니다. 이때 패턴 매칭은 위에서 아래로 진행됩니다. 일치하는 패턴이 없는 경우는 마지막에 보이는 case _: 구문(_를 와일드카드라고 함)이 적용됩니다.
만일 case _: 구문을 명시하지 않았는데 일치하는 패턴도 없다면 아무 일도 일어나지 않습니다. 공식 문서에서는 No-op, 즉 No operation으로 설명합니다.
case문은 |(파이프)로 아래처럼 여러 개를 하나로 합칠 수도 있습니다. 의미는 or 입니다
이 다만 or로 판정할 경우 구문은 정확히 어느포인트에서 걸렸는지 모르기 때문에 아래와 같이 as구문을 사용해서 값을 받을수 있습니다. 단순 리터럴 보다는 Expr식 같은 부분에 사용하면 할당값을 알아낼 수 있습니다
case 401 | 403 | 404 as code:
return "Not allowed"
리터럴과 변수가 함께 포함된 패턴
여기서 부터가 다른 언어와는 좀 다른 부분입니다. 언패킹 (unpacking)과 비슷한 방식이며, 하나의 객체를 여러개로 언팩이 가능합니다. 변수를 바인딩할 때 패턴을 사용할 수도 있습니다. 아래 예에서 어떤 점, point는 x 좌표와 y 좌표로 언패킹됩니다.
#point = 10, 10
def print_point(point):
match point:
case (0, 0):
print("Origin")
case (0, y):
print(f"Y={y}")
case (x, 0):
print(f"X={x}")
case (x, y):
print(f"X={x}, Y={y}")
case _:
raise ValueError("Not a point")
print_point((0, 0)) # Origin
print_point((4, 0)) # X = 4
print_point([0, 2]) # Y = 2
# unpacking이 가능하다면 어떤 꼴이든 상관이 없습니다.
print_point([2] + [4]) # X = 2, Y = 4
print_point([x for x in range(2)]) # Y = 1
print_point(("1", "2")) # X = 1, Y = 2
print_point([1+2, 300+20]) # X = 3, Y = 320
print_point("some thing".split()) # X = some, Y = thing
# unpacking이 불가능하거나 어떤 case에도 걸리지 않으면 ValueError와 마주칩니다.
print_point([1])
print_point([1, 0, 3])
print_point(2, 1) # 두 개의 인자가 들어가면 안됩니다!
결과 :
Origin
X=4
Y=2
X=2, Y=4
Y=1
X=1, Y=2
X=3, Y=320
X=some, Y=thing
Traceback (most recent call last):
File "C:\Users\xxx\PycharmProjects\pythonProject1\main.py", line 29, in <module>
print_point([1])
File "C:\Users\xxx\PycharmProjects\pythonProject1\main.py", line 14, in print_point
raise ValueError("Not a point")
ValueError: Not a point
아래는 이 코드를 실행한 결과입니다. 직관적이죠?
구체적으로 살펴보겠습니다. 첫 번째 패턴인 (0, 0)은 리터럴이 두 개입니다. 그다음 두 개의 패턴에는 리터럴과 변수가 섞여 있습니다. 이 변수는 대상, 즉 point의 값을 바인딩합니다. 네 번째 패턴은 두 개의 값을 캡처합니다. 개념적으로는 언패킹 대입문, 즉 (x, y) = point와 비슷한 거죠.
패턴과 클래스
클래스 이름에 argument list를 포함시켜서 패턴으로 이용할 수도 있습니다. 이렇게 하면 클래스 속성을 변수로 캡처할 수 있습니다.
class Point:
x: int
y: int
def location(point):
match point:
case Point(x=0, y=0):
print("Origin is the point's location.")
case Point(x=0, y=y):
print(f"Y={y} and the point is on the y-axis.")
case Point(x=x, y=0):
print(f"X={x} and the point is on the x-axis.")
case Point():
print("The point is located somewhere else on the plane.")
case _:
print("Not a point")
Wildcard(*) 를 리스트에 넣는 경우
아래와 같이 둘 이상의 리스트가 들어오는 경우, 이것들을 아래와 같이 iterator로 처리할수 있습니다.
def alarm(item):
match item:
case [time, action]:
print(f'Good {time}! It is time to {action}!')
case [time, *actions]:
print('Good morning!')
for action in actions:
print(f'It is time to {action}!')
alarm(['afternoon', 'work'])
alarm(('morning', 'have breakfast', 'brush teeth', 'work'))
출력:
Good afternoon! It is time to work!
Good morning!
It is time to have breakfast!
It is time to brush teeth!
It is time to work!
Sub-patterns
패턴에 패턴을 가지는 경우, 정해진 패턴에서 반응하는 경우 아래 코드에서는 괄호를 이용하여 일치시키려는 “패턴”을 둘러싸고 파이프(|)를 사용하여 이러한 패턴을 분리 할 수 있음을 보여줍니다.
def alarm(item):
match item:
case [('morning' | 'afternoon' | 'evening'), action]:
print(f'Good (?)! It is time to {action}!')
case _:
print('The time is invalid.')
alarm(['arvo','work'])
alarm(['afternoon','work'])
이건 다른예입니다, list안에 class도 들어갈수 있습니다. 패턴은 어떤 식으로도 중첩할 수 있습니다. 아래는 점 리스트인 points를 대조하는 예입니다.
match points:
case []:
print("No points in the list.")
case [Point(0, 0)]:
print("The origin is the only point in the list.")
case [Point(x, y)]:
print(f"A single point {x}, {y} is in the list.")
case [Point(0, y1), Point(0, y2)]:
print(f"Two points on the Y axis at {y1}, {y2} are in the list.")
case _:
print("Something else is found in the list.")
조건이 추가된 분기, 가드
아래와 같이 if문을 붙일수도 있다. 패턴에 if절을 넣을 수 있는데, 이를 가리켜 가드라고 부릅니다. 가드가 거짓이면 match문은 순서상 그다음 case문을 실행합니다. 이때 값이 먼저 캡처된 후에 가드의 참, 거짓이 판단됩니다.
def alarm(item):
match item:
case ['evening', action] if action not in ['work', 'study']:
print(f'You almost finished the day! Now {action}!')
case ['evening', _]:
print('Come on, you deserve some rest!')
case [time, action]:
print(f'Good {time}! It is time to {action}!')
case _:
print('The time is invalid.')
출력:
alarm(['evening','work'])
alarm(['evening','drive'])
alarm(['afternoon','work'])
Come on, you deserve some rest!
You almost finished the day! Now drive!
Good afternoon! It is time to work!
아래는 다른 예제입니다.
match point:
case Point(x, y) if x == y:
print(f"The point is located on the diagonal Y=X at {x}.")
case Point(x, y):
print(f"Point is not on the diagonal.")
복잡한 패턴과 와일드카드(_)
일반적으로 와일드카드(_)는 맨 아래 case문에 단독으로 사용합니다. 그런데 다음처럼 사용할 수도 있습니다.
match test_variable:
case ('warning', code, 40):
print("A warning has been received.")
case ('error', code, _):
print(f"An error {code} occurred.")
여기서 test_variable은 (‘error’, code, 100)과 (‘error’, code, 800)의 경우에 해당합니다.
패턴 매칭의 패턴
Example | Description |
---|
case “a” | 단일 값 “a”에 대해 매칭한다. |
case [“a”, “b”] | 집합 [“a”, “b”]에 대해 매칭한다. |
case [“a”, value] | 2개 값을 가진 집합에 대해 매칭을 하고, 2번째 값을 캡처 변수인 value에 위치시킨다. |
case [“a”, *values] | 1개 이상의 값을 가진 집합에 대해 매칭한다. 다른 값들은 values에 저장한다. 집합 당 별표 표시된 항목을 하나만 포함할 수 있다 (파이썬 함수에서 별표 인수를 사용할 수 있기 때문임). |
case (‘a’|’b’|’c’) | or 연산자( |
case (‘a’|’b’|’c’) as letter | 매칭한 항목을 변수 letter에 넣는 것을 제외하고는 위와 동일하다. |
case [‘a’, value] if <expression> | expression이 ‘참’인 경우에만 캡처를 매칭한다. 표현식에 캡처 변수를 사용할 수 있다. 예를 들어 if value in valid_values를 사용한다면, case는 캡처한 값 value가 집합인 valid_values에 실제 위치한 경우에만 유효하다. |
case [“z”,_] | “z” 로 시작하는 모든 항목의 집합이 매칭한다 |
결론
match state문의 용법은 기본적으로 코드를 간결하게 해주는 목적이 있습니다. if-else문으로는 최대한 비슷하게 하더라도 가독성면에서는 좋지 않은 부분이 있기 때문에 match문은 나름 이를 해결할 방법중의 하나이기도 하고, 리스트라든지 클래스등을 받을수 있기때문에 활용도 측면에서도 나쁘지 않을것으로 기대합니다.