match문
1. match문 사용하기
match문은 Python 3.10부터 도입된 패턴 매칭 구문입니다. if-elif보다 가독성이 좋고, '|'로 여러 패턴을 묶거나 '_' 와일드카드로 기본값을 처리할 수 있습니다.파이썬 3.10 버전부터 추가된 match 문법은 파이썬에 없는 switch문을 사용하기 위해 등장했습니다. 사용자는 match 문법을 이용하여 여러 조건문처럼 여러 경우의 수를 처리할 수 있습니다.
1.1 match문이란?
match문은 여러 조건을 체크하여 해당 조건에 맞는 코드 블록을 실행하는 제어문입니다. switch라는 이름으로 다양한 프로그래밍 언어에서 지원되며, 일반적으로 if-elif-else 문보다 가독성이 좋습니다.
파이썬은 switch 문법을 지원하지 않았습니다. 이로 인해 개발자들은 여러 가지 경우의 수를 처리하기 위해 if-else문이나 dict의 get 메소드를 사용했어야 했습니다. 파이썬에서는 switch문을 if문으로 표현할 수 있으며, 파이썬의 간결성을 해친다고 생각하였습니다. 이러한 논의는 파이썬 3.0부터 진행되어 왔습니다. 보다 자세한 논의는 아래 문서에서 확인할 수 있습니다.
오랫동안 이 논의가 활발히 진행되었고 match 문을 Python 3.10 이상에서 사용할 수 있게 되었습니다.
1.2 match 문법 활용 코드
text = 'Hello World'
match text:
case 'Hello':
print('Hello')
case 'World':
print('World')
case _:
print('No Match') # 결과 'No Match'
- 첫 번째 경우: 'Hello' 문자열과 일치하는지 확인합니다. 일치하지 않으므로 이 경우는 건너뜁니다.
- 두 번째 경우: 'World' 문자열과 일치하는지 확인합니다. 일치하지 않으므로 이 경우도 건너뜁니다.
- 세 번째 경우:
_는 와일드카드로, 어떤 값과도 일치한다는 것을 나타냅니다. 따라서 이 경우가 실행되며 ‘No Match’라는 문자열이 출력됩니다.
위 코드에서 case _는 일종의 '디폴트 케이스'로 작동하며, 어떠한 값도 받아들일 수 있는 와일드카드 역할을 합니다. 이것은 if-elif-else 구조에서의 else 절과 비슷한 작용을 하는데, 어느 조건도 충족하지 않을 때 해당 코드 블록이 실행됩니다.
즉, 'World'라는 문자열은 앞선 두 케이스 조건, 'Hello'와 'World'에 부합하지 않기 때문에, case _:에 지정된 코드 블록이 실행되어 'No Match'가 출력됩니다. match문에 의해 이 문자열은 평가되며, 해당 조건에 맞는 케이스를 찾아 그에 따른 블록을 실행하게 됩니다.
아래 코드는 text가 ‘World’이기 때문에 case 'World'조건 부합하여 print('World')를 실행합니다.
text = 'World'
match text:
case 'Hello':
print('Hello')
case 'World':
print('World')
case _:
print('No Match')
다른 언어에서 일반적으로 사용되는 구분인 아래와 같은 코드는 Python에서는 작동하지 않습니다.
text = '1'
match text:
case '1':
case '2':
print('1, 2')
case _:
print('No Match')
두 조건이 모두 print(’1, 2’)를 수행하길 원했다면 아래와 같은 코드로 작성해야 합니다. 여기서 | 대신 and나 or 연산자는 가능하지 않습니다.
text = '1'
match text:
case '1' | '2':
print('1, 2')
case _:
print('No Match')
2. match 문법 다양한 예제
다음은 구조적 패턴 매칭을 사용하여 i 변수의 값을 여러 경우에 따라 검사하는 test 함수를 정의하고 있습니다. 여러 case 문을 사용하여 다양한 조건을 검사합니다. 아래 예제에서 case 3 | 4는 '3 또는 4일 때'라는 뜻입니다.
def test(i):
match i:
case 1:
print('One')
case 2:
print('Two')
case 3 | 4:
print('Three or Four')
case _ if i > 10:
print('Greater than ten')
case _:
print('Other')
test(1) # 출력: One
test(2) # 출력: Two
test(3) # 출력: Three or Four
test(4) # 출력: Three or Four
test(5) # 출력: Other
test(15) # 출력: Greater than ten
이번에는 값이 아니라 타입에 따라 분기하는 예제입니다. 아래에는 두 개의 함수가 있는데, 하나는 변수 이름 i를 패턴으로 사용했고 다른 하나는 와일드카드 _를 사용했습니다. 둘 다 뒤에 붙은 if 조건으로 실제 판단을 하기 때문에 동작은 같습니다.
여기서 if를 지우면 안 됩니다. case i:나 case _:는 어떤 값이든 받아들이는 패턴이라, 첫 번째 case에서 모든 값을 삼켜버려 뒤의 case에 도달할 수 없기 때문입니다.
def test(i):
match i:
case i if type(i) == int:
print('Integer')
case i if type(i) == str:
print('String')
case i if type(i) == float:
print('Float')
test(1) # 출력: Integer
test('hello') # 출력: String
test(3.14) # 출력: Float
#############################
def test(i):
match i:
case _ if type(i) == int:
print('Integer')
case _ if type(i) == str:
print('String')
case _ if type(i) == float:
print('Float')
test(1) # 출력: Integer
test('hello') # 출력: String
test(3.14) # 출력: Float
해당 예제는 i 변수의 값을 여러 경우에 따라 검사하는 test 함수를 정의하고 있습니다. 여러 case문을 사용하여 다양한 조건을 검사합니다. 다음과 같이 case block 내부에서도 if-else 문을 사용할 수 있습니다.
def test(i):
match i:
case 1:
print('One')
case 2:
print('Two')
case _:
if i > 2:
print('Greater than two')
else:
print('Less than or equal to two')
test(1) # 출력: One
test(2) # 출력: Two
test(3) # 출력: Greater than two
test(4) # 출력: Greater than two
해당 예제에서는 status라는 매개변수를 받아와 그 값이 1, 2, 3 중 하나인지 확인한 후, 해당 값이 맞다면 문자열에 그 값을 포함하여 반환하고, 그렇지 않다면 'hello'를 반환하는 함수를 정의하고 있습니다. as 키워드를 사용하여 변수명으로 사용할 수 있습니다.
def test(status):
match status:
case (1 | 2 | 3) as code:
return f'value : {code}'
case _:
return 'hello'
print(test(2)) # 출력: value : 2
해당 예제는 status라는 매개변수의 값을 검사하고, 그 값이 1, 2, 3 중 하나이며 동시에 짝수인지 확인합니다. 해당 조건이 맞다면 'hello'를 반환하고, 그렇지 않다면 'world'를 반환하는 함수를 정의하고 있습니다.
def test(status):
match status:
case (1 | 2 | 3) if status % 2 == 0:
return 'hello'
case _:
return 'world'
print(test(3)) # 출력: world
3. 나아가기
match 문법은 조건이 많아질수록 if-elif 나열보다 읽기 편합니다. 성능은 어떨까요? 조건 10개를 두고, 첫 번째 조건에서 바로 걸리는 경우(Best Case)와 마지막 조건까지 가야 하는 경우(Worst Case)를 각각 100만 번씩 측정해보았습니다.
if와 match는 어느 쪽이든 차이가 크지 않습니다. 앞에서 걸릴 때는 if가 조금 빠르고, 끝까지 갈 때는 match가 조금 빠른 정도입니다. 반면 dict.get()은 조건이 몇 번째에 있든 시간이 거의 같습니다. 해시 테이블로 한 번에 찾아가기 때문입니다.
정리하면, 단순히 값 하나를 다른 값으로 바꾸는 분기라면 dict.get()이 가장 유리하고, if와 match 사이에서는 성능보다 가독성을 기준으로 고르시면 됩니다. 아래 측정 결과는 colab 환경에서 얻은 것이며, 실행할 때마다 그리고 환경에 따라 달라질 수 있습니다.
# match, if, dict.get문 성능 비교
테스트 키: key_0
If-Elif 방식 (10 조건): 0.159554 초
Match-Case 방식 (10 조건): 0.170885 초
Dict.get() 방식 (10 항목): 0.205562 초
테스트 키: key_9
If-Elif 방식 (10 조건): 0.496979 초
Match-Case 방식 (10 조건): 0.461650 초
Dict.get() 방식 (10 항목): 0.203502 초
테스트 코드입니다.
import timeit
# 10개의 키-값 쌍 생성
data = {f"key_{i}": f"value_{i}" for i in range(10)}
# 테스트할 키
# Best
# test_key = 'key_0'
# Worst
test_key = 'key_9'
def using_if(key):
if key == "key_0":
return "value_0"
elif key == "key_1":
return "value_1"
elif key == "key_2":
return "value_2"
elif key == "key_3":
return "value_3"
elif key == "key_4":
return "value_4"
elif key == "key_5":
return "value_5"
elif key == "key_6":
return "value_6"
elif key == "key_7":
return "value_7"
elif key == "key_8":
return "value_8"
elif key == "key_9":
return "value_9"
else:
return "Not found"
def using_match(key):
match key:
case "key_0":
return "value_0"
case "key_1":
return "value_1"
case "key_2":
return "value_2"
case "key_3":
return "value_3"
case "key_4":
return "value_4"
case "key_5":
return "value_5"
case "key_6":
return "value_6"
case "key_7":
return "value_7"
case "key_8":
return "value_8"
case "key_9":
return "value_9"
case _:
return "Not found"
def using_dict_get(key):
return data.get(key, "Not found")
# 벤치마크 함수
def benchmark_if():
return using_if(test_key)
def benchmark_match():
return using_match(test_key)
def benchmark_dict_get():
return using_dict_get(test_key)
# 벤치마크 실행
number = 1000000
if_time = timeit.timeit(benchmark_if, number=number)
match_time = timeit.timeit(benchmark_match, number=number)
dict_get_time = timeit.timeit(benchmark_dict_get, number=number)
print(f"테스트 키: {test_key}")
print(f"If-Elif 방식 (10 조건): {if_time:.6f} 초")
print(f"Match-Case 방식 (10 조건): {match_time:.6f} 초")
print(f"Dict.get() 방식 (10 항목): {dict_get_time:.6f} 초")
# 결과 검증
print("\n결과 검증:")
print(f"If-Elif 결과: {using_if(test_key)}")
print(f"Match-Case 결과: {using_match(test_key)}")
print(f"Dict.get() 결과: {using_dict_get(test_key)}")