WeniVooks

검색

견고한 파이썬

match문

1. match문 사용하기

파이썬 3.10 버전부터 추가된 match 문법은 파이썬에 없는 switch문을 사용하기 위해 등장했습니다. 사용자는 match 문법을 이용하여 여러 조건문처럼 여러 경우의 수를 처리할 수 있습니다.

1.1 match문이란?

match문은 여러 조건을 체크하여 해당 조건에 맞는 코드 블록을 실행하는 제어문입니다. switch라는 이름으로 다양한 프로그래밍 언어에서 지원되며, 일반적으로 if-elif-else 문보다 가독성이 좋습니다.

파이썬은 switch 문법을 지원하지 않았습니다. 이로 인해 개발자들은 여러 가지 경우의 수를 처리하기 위해 if-else문이나 dictget 메소드를 사용했어야 했습니다. 파이썬에서는 switch문을 if문으로 표현할 수 있으며, 파이썬의 간결성을 해친다고 생각하였습니다. 이러한 논의는 파이썬 3.0부터 진행되어 왔습니다. 보다 자세한 논의는 아래 문서에서 확인할 수 있습니다.

PEP 3103 – A Switch/Case Statement | peps.python.org

오랫동안 이 논의가 활발히 진행되었고 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’)를 수행하길 원했다면 아래와 같은 코드로 작성해야 합니다. 여기서 | 대신 andor 연산자는 가능하지 않습니다.

text = '1' match text: case '1' | '2': print('1, 2') case _: print('No Match')

2. match 문법 다양한 예제

다음은 구조적 패턴 매칭을 사용하여 i 변수의 값을 여러 경우에 따라 검사하는 test 함수를 정의하고 있습니다. 여러 case 문을 사용하여 다양한 조건을 검사합니다. 아래 예제에서 case 3 | 4 구문은 case 3 or case 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

각 함수는 입력된 변수의 타입을 확인하고 해당 타입의 이름을 출력합니다. 두 코드 조각은 비슷하지만 변수 이름을 패턴으로 사용하는 것과 와일드카드 패턴을 사용하는 것에 차이가 있습니다. 아래 예제에서는 if문을 사용하여 type을 비교하여 다양한 값을 처리합니다. 아래 코드에서 if를 제거하는 것은 허용하지 않습니다.

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문법이나 dictget 메서드보다 가독성이 좋습니다. 성능은 if와 비교하면 그 차이는 미미합니다. 아래 소스코드는 Best CaseWorst Case를 비교한 결과입니다. 조건이 2개만 되어도 dict.get()이 가장 빠릅니다. 나머지 2개만 비교했을 때에는 베스트일 경우에는 if문이, 워스트일 경우에는 match문이 빠릅니다. 실행은 colab 환경에서 진행하였습니다. 해당 실행 결과는 실행할 때마다, 환경에 따라 다를 수 있습니다. 여기서 중요한 포인트는 두 차이가 미미하다는 것과 조건이 2개만 되어도 get() 메서드가 가장 빠르다는 것입니다.

# 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
# 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)}")
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)}")
{"packages":["numpy","pandas","matplotlib","lxml"]}
8.2 if, elif, else문과 중첩 조건문9장 반복문