13. match와 case로 선택하기(구조적 패턴 매칭)
프로그램이 여러 가능한 값이나 패턴에 따라 결정을 내려야 할 때, 여러분은 이미 8장에서 if-elif-else 체인을 사용하는 방법을 배웠습니다. Python 3.10에서는 match와 case 문을 사용하는 구조적 패턴 매칭(structural pattern matching) 이라는 강력한 대안을 도입했습니다. 이 기능은 복잡한 의사결정 시나리오를 더 깔끔하고 더 표현력 있게 처리할 수 있는 방법을 제공합니다.
패턴 매칭(pattern matching)은 단순한 값 비교를 넘어섭니다. 데이터의 구조와 형태에 대해 매칭하고, 복잡한 객체에서 값을 추출하며, 다중 분기 결정을 더 읽기 쉬운 형태로 표현할 수 있게 해줍니다. if-elif-else 체인은 많은 상황에서 충분히 잘 동작하지만, match-case 문은 여러 개의 뚜렷한 경우를 다룰 때, 특히 구조화된 데이터로 작업할 때 진가를 발휘합니다.
13.1) match와 case 문 소개(8장의 if-elif 기반)
13.1.1) match-case의 기본 구조
match 문은 subject(대상) 라고 부르는 값을 검사하고, case 절에 정의된 하나 이상의 패턴(pattern) 과 비교합니다. 패턴이 매칭되면 Python은 해당 case와 연결된 코드 블록을 실행합니다.
기본 구조는 다음과 같습니다:
match subject:
case pattern1:
# pattern1이 매칭되면 실행할 코드
case pattern2:
# pattern2가 매칭되면 실행할 코드
case pattern3:
# pattern3이 매칭되면 실행할 코드기본 개념을 보여주는 간단한 예시부터 시작해 보겠습니다:
# 간단한 HTTP 상태 코드 처리기
status_code = 404
match status_code:
case 200:
print("Success: Request completed")
case 404:
print("Error: Page not found")
case 500:
print("Error: Server error")Output:
Error: Page not found이 예시에서 match 문은 status_code를 subject(대상) 로 삼아 검사합니다. Python은 각 case 패턴을 순서대로 확인합니다. status_code가 404와 같다는 것을 찾으면, 해당 코드 블록을 실행한 뒤 match 문을 빠져나옵니다. 나머지 case들은 검사하지 않습니다.
13.1.2) match-case가 if-elif-else와 다른 점
이런 생각이 들 수도 있습니다. “이거 if-elif-else로도 쓸 수 있지 않나요?” 네, 가능합니다:
status_code = 404
if status_code == 200:
print("Success: Request completed")
elif status_code == 404:
print("Error: Page not found")
elif status_code == 500:
print("Error: Server error")Output:
Error: Page not found두 버전 모두 같은 결과를 만들어냅니다. 하지만 match-case에는 몇 가지 장점이 있습니다:
- 의도가 더 명확함:
match문은 하나의 값을 여러 가능성과 비교해 확인하고 있다는 점을 명시적으로 보여줍니다 - 반복이 적음: 모든 비교에서 변수 이름을 반복해서 쓰지 않아도 됩니다
- 더 강력한 패턴: 앞으로 보겠지만
match-case는 단순한 동등 비교보다 훨씬 더 많은 일을 할 수 있습니다 - 가독성이 더 좋음: 복잡한 결정 트리에서는
match-case가 더 이해하기 쉬운 경우가 많습니다
13.1.3) 어떤 패턴도 매칭되지 않을 때
어떤 패턴도 매칭되지 않으면 어떻게 될까요? match 문은 어떤 case 블록도 실행하지 않고 그냥 끝납니다:
# 사용자 역할 확인기
user_role = "guest"
match user_role:
case "admin":
print("Full system access granted")
case "moderator":
print("Content management access granted")
case "editor":
print("Editing access granted")
print("Role check complete")Output:
Role check complete"guest"는 어떤 패턴과도 매칭되지 않기 때문에 어떤 case 블록도 실행되지 않습니다. 프로그램은 match 문 다음 코드로 계속 진행합니다. 이 동작은 이해해 두는 것이 중요합니다—if-elif-else 체인에서는 마지막에 else 절을 추가해 나머지 모든 경우를 잡아낼 수 있지만, 모든 경우를 잡는 패턴이 없는 기본 match 문은 매칭되는 패턴이 없을 때 조용히 아무 것도 하지 않습니다.
13.1.4) 실전 예시: 메뉴 선택 시스템
사용자 선택을 처리할 때 match-case가 얼마나 명확한지 보여주는 더 완전한 예시를 만들어 보겠습니다:
# 레스토랑 주문 시스템
menu_choice = 3
match menu_choice:
case 1:
item = "Caesar Salad"
price = 8.99
print(f"You ordered: {item} - ${price}")
case 2:
item = "Grilled Chicken"
price = 14.99
print(f"You ordered: {item} - ${price}")
case 3:
item = "Vegetable Pasta"
price = 12.99
print(f"You ordered: {item} - ${price}")
case 4:
item = "Chocolate Cake"
price = 6.99
print(f"You ordered: {item} - ${price}")
print("Order submitted to kitchen")Output:
You ordered: Vegetable Pasta - $12.99
Order submitted to kitchen이 예시는 각 case가 여러 문장을 포함할 수 있음을 보여줍니다. menu_choice가 3과 매칭되면 Python은 해당 case 블록의 세 줄을 모두 실행합니다. 즉 item 할당, price 할당, 주문 확인 출력입니다.
13.2) _ 와일드카드, 리터럴 패턴, 다중 패턴 사용하기
13.2.1) 와일드카드 패턴: 나머지 모두 잡기
언더스코어 _는 무엇이든 매칭되는 특별한 패턴입니다. 보통 마지막 case로 사용하여 앞선 패턴들과 매칭되지 않은 모든 값을 처리합니다—if-elif-else 체인의 마지막 else 절과 비슷합니다:
# 기본 case가 있는 HTTP 상태 코드 처리기
status_code = 403
match status_code:
case 200:
print("Success: Request completed")
case 404:
print("Error: Page not found")
case 500:
print("Error: Server error")
case _:
print(f"Unhandled status code: {status_code}")Output:
Unhandled status code: 403_ 패턴은 모든 경우를 포괄하는 역할을 합니다. 403은 구체적인 case 중 어떤 것과도 매칭되지 않으므로, 와일드카드 패턴이 매칭되어 블록이 실행됩니다. 와일드카드 패턴은 어떤 값과도 매칭되기 때문에 항상 마지막에 놓아야 합니다—그 뒤에 오는 case는 절대 실행되지 않습니다.
실제로 와일드카드가 왜 유용한지 살펴보겠습니다:
# 요일 스케줄러
day = "Saturday"
match day:
case "Monday":
print("Team meeting at 9 AM")
case "Wednesday":
print("Project review at 2 PM")
case "Friday":
print("Weekly report due")
case _:
print(f"{day}: No scheduled events")Output:
Saturday: No scheduled events와일드카드 패턴이 없다면 day가 "Saturday", "Sunday" 또는 다른 어떤 값이었을 때 match 문은 아무 출력도 없이 조용히 끝납니다. 와일드카드는 예상치 못하거나 명시되지 않은 경우도 문제 없이 처리할 수 있게 해줍니다.
13.2.2) 리터럴 패턴: 특정 값 매칭하기
리터럴 패턴(literal patterns) 은 정확한 값과 매칭됩니다. 우리는 이미 이것을 사용해 왔습니다—숫자, 문자열, 불리언 값은 모두 리터럴 패턴입니다:
# 신호등 컨트롤러
light_color = "yellow"
match light_color:
case "green":
print("Go")
case "yellow":
print("Caution: Light changing soon")
case "red":
print("Stop")
case _:
print("Invalid light color")Output:
Caution: Light changing soon서로 다른 타입의 리터럴 패턴을 사용할 수 있으며, match는 값과 타입을 모두 비교합니다:
# 설정 검증기(서로 다른 리터럴 타입 사용)
setting_value = True
match setting_value:
case True: # boolean 리터럴
print("Feature enabled")
case False: # boolean 리터럴
print("Feature disabled")
case None: # None 리터럴
print("Feature not configured")
case 0: # 정수 리터럴
print("Feature explicitly turned off")
case "auto": # 문자열 리터럴
print("Feature set to automatic mode")
case _:
print("Invalid configuration value")Output:
Feature enabled리터럴 패턴은 정수, 실수, 문자열, 불리언, None과 함께 동작합니다. Python은 == 연산자와 같은 규칙으로 동등성 검사를 합니다.
13.2.3) OR 연산자로 여러 패턴 사용하기
때로는 여러 다른 값에 대해 동일한 코드를 실행하고 싶을 수 있습니다. |(파이프) 연산자를 사용해 여러 패턴을 결합할 수 있는데, 이는 “또는(or)”를 의미합니다:
# 주말 판별기
day = "Saturday"
match day:
case "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday":
print("It's a weekday - time to work!")
case "Saturday" | "Sunday":
print("It's the weekend - time to relax!")
case _:
print("Invalid day name")Output:
It's the weekend - time to relax!| 연산자를 사용하면 같은 동작을 수행해야 하는 여러 패턴을 지정할 수 있습니다. 대상이 |로 구분된 어떤 패턴과라도 매칭되면 해당 case가 실행됩니다. 이는 동일한 코드 블록을 가진 별도의 case들을 여러 개 쓰는 것보다 훨씬 깔끔합니다.
|로 서로 다른 타입의 패턴을 섞을 수도 있습니다:
# 예/아니오 질문용 입력 검증기
response = "yes"
match response:
case True | "yes":
print("You confirmed the action")
case False | "no":
print("You cancelled the action")
case _:
print("Please answer yes or no")Output:
You confirmed the action13.2.4) as로 어떤 대안이 매칭됐는지 캡처하기
|로 여러 패턴을 사용할 때, 어떤 특정 값이 매칭되었는지 알고 싶을 수 있습니다. as 키워드를 사용하면 매칭된 값을 캡처할 수 있습니다:
# 그룹화된 응답을 사용하는 상태 코드 처리기
status = 201
match status:
case 200 | 201 | 202 | 204 as success_code:
print(f"Success: {success_code}")
case 400 | 401 | 403 | 404 as client_error:
print(f"Client error: {client_error}")
case 500 | 502 | 503 as server_error:
print(f"Server error: {server_error}")
case _:
print("Unknown status code")Output:
Success: 201as 키워드는 어떤 대안이 매칭되었든 그 값을 캡처하는 바인딩 변수를 생성합니다. 이 예시에서는 200 | 201 | 202 | 204 대안 중 실제로 매칭된 값이 201이므로 success_code는 201에 바인딩됩니다.
다음은 로깅에서 이것이 어떻게 유용한지 보여주는 또 다른 예시입니다:
# 로그 레벨 처리기
log_level = "WARN"
match log_level:
case "DEBUG" | "TRACE" as level:
print(f"Verbose logging: {level}")
print("Detailed diagnostic information will be recorded")
case "INFO" | "NOTICE" as level:
print(f"Informational: {level}")
print("Normal operation messages will be recorded")
case "WARN" | "WARNING" as level:
print(f"Warning level: {level}")
print("Potential issues detected")
case "ERROR" | "FATAL" | "CRITICAL" as level:
print(f"Error level: {level}")
print("Immediate attention required")
case _:
print("Unknown log level")Output:
Warning level: WARN
Potential issues detected13.3) 바인딩 변수로 값 추출하기
13.3.1) 바인딩 변수란 무엇인가요?
지금까지는 리터럴 값과 매칭해 왔습니다. 하지만 매칭하려는 데이터의 일부를 캡처(capture) 하거나 추출(extract) 할 수 있을 때 패턴 매칭은 진정으로 강력해집니다. 바인딩 변수(binding variable) ( 캡처 패턴(capture pattern) 이라고도 함)는 패턴에서 매칭된 값을 캡처하고, case 블록에서 사용할 수 있도록 해주는 이름입니다.
간단한 예시를 보겠습니다:
# 간단한 값 캡처
command = "save"
match command:
case "quit":
print("Exiting program")
case action: # 이것은 바인딩 변수입니다
print(f"Executing action: {action}")Output:
Executing action: saveaction 패턴은 바인딩 변수입니다. 이는 와일드카드 _처럼 어떤 값과도 매칭되지만, _와 달리 그 값을 캡처하여 action이라는 이름에 할당합니다. case 블록 안에서는 action을 사용해 매칭된 값을 참조할 수 있습니다.
중요한 차이점: 바인딩 변수는 _처럼 무엇이든 매칭됩니다. 차이점은 _는 값을 버리는 반면, 바인딩 변수는 case 블록에서 사용하기 위해 값을 캡처한다는 점입니다.
13.3.2) 바인딩 변수 vs 와일드카드
바인딩 변수와 와일드카드를 직접 비교해 보겠습니다:
# 와일드카드 사용 - 값이 캡처되지 않음
status = 403
match status:
case 200:
print("Success")
case _:
print("Some other status code") # 실제 값에 접근할 수 없음Output:
Some other status code이제 바인딩 변수를 사용해 보겠습니다:
# 바인딩 변수 사용 - 값이 캡처됨
status = 403
match status:
case 200:
print("Success")
case code: # 바인딩 변수가 값을 캡처함
print(f"Status code {code} received")Output:
Status code 403 received바인딩 변수 code는 403 값을 캡처하여 case 블록 안에서 사용할 수 있게 합니다. 이는 특정 패턴과 매칭되지 않은 실제 값으로 작업해야 할 때 유용합니다.
13.3.3) 튜플 패턴 매칭과 구성 요소 추출하기
패턴 매칭은 튜플 같은 구조화된 데이터에서 특히 강력합니다. 튜플의 형태를 매칭하면서 동시에 구성 요소를 추출할 수 있습니다. 15장에서 튜플을 자세히 공부하겠지만, 이 예시는 match 문에서 튜플 패턴이 어떻게 동작하는지만 다룹니다.
# 좌표계 - 튜플 패턴 매칭
point = (3, 7)
match point:
case (0, 0):
print("Origin point")
case (0, y): # y축 위의 어떤 점과도 매칭
print(f"On y-axis at y={y}")
case (x, 0): # x축 위의 어떤 점과도 매칭
print(f"On x-axis at x={x}")
case (x, y): # 그 외 어떤 점과도 매칭
print(f"Point at coordinates ({x}, {y})")Output:
Point at coordinates (3, 7)무슨 일이 일어나는지 쪼개서 살펴보겠습니다:
- 대상
point는 튜플(3, 7)입니다 - Python은 각 case 패턴을 순서대로 확인합니다
- 처음 세 패턴은 튜플의 특정 위치에
0이라는 리터럴 값이 정확히 일치해야 하지만,point의 값(3, 7)에는 해당 위치에0이 없기 때문에 매칭되지 않습니다 (x, y)패턴은 두 원소를 가진 튜플이기 때문에 매칭됩니다- Python은
x를3에,y를7에 바인딩합니다 - 캡처된 값과 함께 case 블록이 실행됩니다
다음은 서로 다른 튜플 패턴을 보여주는 또 다른 예시입니다:
# RGB 색상 분석기
color = (255, 0, 0)
match color:
case (0, 0, 0):
print("Black")
case (255, 255, 255):
print("White")
case (r, 0, 0): # 강도가 변하는 순수한 빨강
print(f"Pure red with intensity {r}")
case (0, g, 0): # 순수한 초록
print(f"Pure green with intensity {g}")
case (0, 0, b): # 순수한 파랑
print(f"Pure blue with intensity {b}")
case (r, g, b): # 그 외 어떤 색이든
print(f"RGB color: red={r}, green={g}, blue={b}")Output:
Pure red with intensity 255이 입력은 (r, 0, 0) 패턴과 매칭됩니다. 튜플이 세 원소를 가지고, 마지막 두 원소가 0이며, 첫 번째 값이 r에 바인딩되기 때문입니다.
13.3.4) 리스트 패턴 매칭하기
리스트 패턴도 매칭하면서 원소를 추출할 수 있습니다. 14장에서 리스트를 자세히 다루겠지만, 지금은 match 문에서 리스트 패턴이 어떻게 동작하는지에 초점을 맞춥니다:
# 인자가 있는 명령
command = ["move", "north", "5"]
match command:
case ["quit"]:
print("Exiting game")
case ["look"]:
print("You look around the room")
case ["move", direction]:
print(f"Moving {direction}")
case ["move", direction, distance]:
print(f"Moving {direction} for {distance} steps")
case _:
print("Unknown command")Output:
Moving north for 5 steps["move", direction, distance] 패턴은 첫 번째 원소가 "move"인 3원소 리스트와 매칭됩니다. 두 번째 원소는 direction으로, 세 번째 원소는 distance로 캡처합니다.
다음은 길이가 달라지는 리스트를 다루는 실용적인 예시입니다:
# 장바구니 항목 처리기
item = ["laptop", 999.99, 2]
match item:
case [name]: # 이름만 있는 항목
print(f"Item: {name} (no price or quantity specified)")
case [name, price]: # 이름과 가격이 있는 항목
print(f"Item: {name}, Price: ${price}, Quantity: 1 (default)")
case [name, price, quantity]: # 완전한 항목 정보
total = price * quantity
print(f"Item: {name}, Price: ${price}, Quantity: {quantity}")
print(f"Subtotal: ${total}")
case _:
print("Invalid item format")Output:
Item: laptop, Price: $999.99, Quantity: 2
Subtotal: $1999.98리스트가 정확히 3개 원소를 가지므로 [name, price, quantity] case가 실행되며, 각 원소는 해당 변수에 바인딩됩니다.
13.3.5) 딕셔너리 패턴 매칭하기
패턴 매칭은 딕셔너리에서도 동작하며, 특정 키를 매칭하고 값을 추출할 수 있습니다. 16장에서 딕셔너리를 자세히 공부하겠지만, 이 절은 match 문에서 딕셔너리 패턴이 동작하는 방식에만 집중합니다.
# 사용자 프로필 처리기
user = {"name": "Alice", "role": "admin", "active": True}
match user:
case {"role": "admin", "active": True}:
print("Active administrator - full access granted")
case {"role": "admin", "active": False}:
print("Inactive administrator - access suspended")
case {"role": role, "active": True}: # role 값을 캡처
print(f"Active user with role: {role}")
case {"role": role, "active": False}:
print(f"Inactive user with role: {role}")
case _:
print("Invalid user profile")Output:
Active administrator - full access granted{"role": "admin", "active": True} case는 딕셔너리 패턴이 키–값 쌍 매칭을 요구하고, 더 일반적인 패턴들보다 앞에서 이 정확한 매칭이 먼저 검사되기 때문에 실행됩니다.
딕셔너리 패턴은 유연합니다—딕셔너리에 추가 키가 있더라도 지정된 키가 지정된 값으로 존재하면 매칭됩니다:
# API 응답 처리기
response = {"status": "success", "data": {"id": 123, "name": "Product"}, "timestamp": "2025-12-17"}
match response:
case {"status": "error", "message": msg}:
print(f"Error occurred: {msg}")
case {"status": "success", "data": data}:
print(f"Success! Data received: {data}")
case _:
print("Unknown response format")Output:
Success! Data received: {'id': 123, 'name': 'Product'}{"status": "success", "data": data} 패턴은 딕셔너리에 "timestamp" 키가 추가로 있음에도 매칭됩니다. 이 패턴은 지정된 키들이 지정된 값(또는 패턴)으로 존재하기만 하면 됩니다.
13.3.6) 리터럴과 바인딩 변수를 결합하기
리터럴 패턴과 바인딩 변수를 섞어 정교한 매칭 로직을 만들 수 있습니다: 앞서 구조와 위치 매칭에 집중했던 튜플 예시들과 달리, 이 예시는 리터럴 값과 바인딩 변수를 결합해 실제 의사결정 로직을 구현하는 방법을 보여줍니다.
# HTTP 요청 라우터
request = ("GET", "/api/users", 42)
match request:
case ("GET", "/", None):
print("Homepage request")
case ("GET", path, None):
print(f"GET request for: {path}")
case ("POST", path, data):
print(f"POST request to {path} with data: {data}")
case ("GET", path, user_id):
print(f"GET request for {path} with user ID: {user_id}")
case _:
print("Unsupported request type")Output:
GET request for /api/users with user ID: 42이 예시는 "GET"처럼 특정 값을 매칭하면서도, 같은 패턴 안에서 path, user_id처럼 다른 값은 캡처할 수 있음을 보여줍니다.
13.3.7) 실전 예시: 이벤트 핸들러
바인딩 변수의 강력함을 보여주는 완전한 예시를 만들어 보겠습니다:
# 게임 이벤트 핸들러
event = ("player_move", {"x": 10, "y": 5, "speed": 2})
match event:
case ("player_move", {"x": x, "y": y}):
print(f"Player moved to position ({x}, {y})")
case ("player_attack", {"target": target, "damage": damage}):
print(f"Player attacked {target} for {damage} damage")
case ("item_pickup", {"item": item_name}):
print(f"Player picked up: {item_name}")
case ("game_over", {"score": final_score}):
print(f"Game ended. Final score: {final_score}")
case (event_type, data):
print(f"Unknown event type: {event_type}")
print(f"Event data: {data}")Output:
Player moved to position (10, 5)이 이벤트 핸들러는 이벤트 타입과 이벤트 데이터 딕셔너리를 담은 튜플을 매칭합니다. 이벤트 타입에 따라 딕셔너리에서 특정 값을 추출하므로, 다양한 종류의 이벤트를 깔끔하고 읽기 쉬운 코드로 처리할 수 있습니다.
13.4) if 가드로 추가 조건 더하기
13.4.1) 가드란 무엇인가요?
때로는 패턴을 매칭하면서 추가 조건 도 확인해야 합니다. if 가드(if guard) 는 if 키워드를 사용해 case 패턴에 추가할 수 있는 조건입니다. 패턴이 매칭되고 가드 조건까지 참일 때만 해당 case가 매칭됩니다.
문법은 다음과 같습니다:
match subject:
case pattern if condition:
# pattern이 매칭되고 condition도 True일 때만 코드가 실행됩니다간단한 예시를 보겠습니다:
# 나이에 따른 접근 제어
age = 16
match age:
case age if age >= 18:
print("Adult - full access granted")
case age if age >= 13:
print("Teen - limited access granted")
case age if age >= 0:
print("Child - parental supervision required")
case _:
print("Invalid age")Output:
Teen - limited access granted이 예시에서 바인딩 변수 age가 값을 캡처하고, if age >= 13 가드가 추가 조건을 더합니다. 이 case는 값이 13 이상일 때만 매칭됩니다. age가 16이므로 두 번째 case가 매칭되어 실행됩니다.
13.4.2) 가드 평가 방식
평가 순서를 이해하는 것은 중요합니다. 다음은 가드가 패턴 매칭과 어떻게 상호작용하는지 보여주는 자세한 시각화입니다:
Python은 먼저 패턴이 매칭되는지 확인합니다. 패턴이 매칭되는 경우에만 가드 조건을 평가합니다. 가드가 거짓이면, 패턴이 매칭되었더라도 Python은 다음 case로 넘어갑니다.
이를 보여주는 예시입니다:
# 온도 경고 시스템
temperature = 25
match temperature:
case temp if temp > 35:
print(f"Extreme heat warning: {temp}°C")
case temp if temp > 30:
print(f"High temperature alert: {temp}°C")
case temp if temp > 20:
print(f"Comfortable temperature: {temp}°C")
case temp if temp > 10:
print(f"Cool temperature: {temp}°C")
case temp:
print(f"Cold temperature: {temp}°C")Output:
Comfortable temperature: 25°C각 case는 바인딩 변수 temp로 온도 값을 캡처한 다음, 가드를 적용해 특정 범위에 속하는지 확인합니다. case는 순서대로 검사되므로, 가드가 참인 첫 번째 매칭 case가 실행됩니다.
13.4.3) 리터럴 패턴과 함께 사용하는 가드
가드를 리터럴 패턴과 결합해 더 구체적인 매칭을 만들 수 있습니다:
# 품목 종류와 수량에 따른 할인 계산기
item = ("book", 5)
match item:
case ("book", quantity) if quantity >= 10:
discount = 0.20 # 10권 이상 책에 대한 20% 할인
print(f"Bulk book order: {quantity} books, {discount*100}% discount")
case ("book", quantity) if quantity >= 5:
discount = 0.10 # 5~9권 책에 대한 10% 할인
print(f"Book order: {quantity} books, {discount*100}% discount")
case ("book", quantity):
discount = 0.0 # 5권 미만은 할인 없음
print(f"Book order: {quantity} books, no discount")
case (item_type, quantity):
print(f"Order: {quantity} {item_type}(s)")Output:
Book order: 5 books, 10.0% discount("book", quantity) 패턴은 첫 번째 원소가 "book"인 튜플과 매칭됩니다. if quantity >= 5 가드는 수량이 최소 5개여야 한다는 조건을 추가합니다.
13.4.4) 복잡한 조건을 가진 가드
가드는 and, or, not을 포함한 어떤 불리언 표현식도 사용할 수 있으며, 복잡한 조건도 포함할 수 있습니다:
# 출석을 고려한 학생 성적 평가기
student = {"name": "Bob", "grade": 85, "attendance": 75}
match student:
case {"grade": g, "attendance": a} if g >= 90 and a >= 90:
status = "Excellent"
print(f"Grade: {g}, Attendance: {a}% - Status: {status}")
case {"grade": g, "attendance": a} if g >= 80 and a >= 80:
status = "Good"
print(f"Grade: {g}, Attendance: {a}% - Status: {status}")
case {"grade": g, "attendance": a} if g >= 70 and a >= 70:
status = "Satisfactory"
print(f"Grade: {g}, Attendance: {a}% - Status: {status}")
case {"grade": g, "attendance": a} if g >= 60 or a >= 60:
status = "Needs Improvement"
print(f"Grade: {g}, Attendance: {a}% - Status: {status}")
case _:
print("Failing - immediate intervention required")Output:
Grade: 85, Attendance: 75% - Status: Satisfactoryif g >= 70 and a >= 70 가드는 성적과 출석률이 모두 70 이상이어야 한다고 요구합니다. Bob은 성적이 85이고 출석률이 75%이므로 이 case가 매칭됩니다.
13.4.5) 실전 예시: 사용자 인증 시스템
가드를 사용해 현실적인 인증 시스템을 구현하는 완전한 예시를 만들어 보겠습니다:
# 역할 기반 접근을 포함한 사용자 인증
user = {"username": "alice", "role": "admin", "active": True, "login_attempts": 0}
match user:
case {"active": False}:
print("Account suspended - contact administrator")
case {"login_attempts": attempts} if attempts >= 3:
print("Account locked due to too many failed login attempts")
case {"role": "admin", "active": True}:
print("Admin access granted - full system privileges")
case {"role": "moderator", "active": True}:
print("Moderator access granted - content management privileges")
case {"role": role, "active": True} if role in ["editor", "author"]:
print(f"{role.capitalize()} access granted - content creation privileges")
case {"role": "user", "active": True}:
print("User access granted - basic privileges")
case _:
print("Access denied - invalid user profile")Output:
Admin access granted - full system privileges이 예시는 가드로 복잡한 비즈니스 로직을 구현할 수 있음을 보여줍니다. 시스템은 계정 상태, 로그인 시도 횟수, 역할 기반 권한 등 여러 조건을 확인합니다. 각 case는 특정 시나리오를 처리하므로 인증 로직이 명확하고 유지보수하기 쉬워집니다.
13.5) 단순한 시퀀스와 매핑 형태로 매칭하기
13.5.1) 가변 길이 시퀀스 매칭하기
때로는 길이가 다양한 시퀀스를 매칭해야 합니다. Python의 패턴 매칭은 * 연산자로 이를 지원하며, 0개 이상의 원소를 캡처합니다.
# 가변 인자를 가진 명령 파서
command = ["copy", "file1.txt", "file2.txt", "file3.txt", "backup/"]
match command:
case ["help"]:
print("Available commands: copy, move, delete")
case ["copy", source, destination]:
print(f"Copying {source} to {destination}")
case ["copy", *sources, destination]:
print(f"Copying {len(sources)} files to {destination}")
print(f"Source files: {sources}")
case ["delete", *files]:
print(f"Deleting {len(files)} file(s): {files}")
case _:
print("Unknown command")Output:
Copying 3 files to backup/
Source files: ['file1.txt', 'file2.txt', 'file3.txt']["copy", *sources, destination] 패턴은 "copy"로 시작하고 destination으로 끝나며, 그 사이에 어떤 개수의 source 파일이든 포함하는 리스트와 매칭됩니다. *sources는 중간의 모든 원소를 리스트로 캡처합니다.
중요: 시퀀스 패턴에서는 * 패턴을 하나만 사용할 수 있으며, 원소를 리스트로 캡처합니다:
# 로그 엔트리 파서
log_entry = ["2025-12-17", "10:30:45", "ERROR", "Database", "connection", "timeout"]
match log_entry:
case [date, time, "ERROR", *error_details]:
print(f"Error on {date} at {time}")
print(f"Error details: {' '.join(error_details)}")
case [date, time, "WARNING", *warning_details]:
print(f"Warning on {date} at {time}")
print(f"Warning details: {' '.join(warning_details)}")
case [date, time, level, *message]:
print(f"{level} on {date} at {time}: {' '.join(message)}")Output:
Error on 2025-12-17 at 10:30:45
Error details: Database connection timeout13.5.2) 시퀀스 패턴과 가드 결합하기
시퀀스 패턴과 함께 가드를 사용해 추가 조건을 더할 수 있습니다:
# 성적 리스트 분석기
grades = [85, 92, 78, 95, 88]
match grades:
case []:
print("No grades recorded")
case [grade] if grade >= 90:
print(f"Single excellent grade: {grade}")
case [grade] if grade < 60:
print(f"Single failing grade: {grade}")
case [*all_grades] if len(all_grades) >= 5 and sum(all_grades) / len(all_grades) >= 90:
average = sum(all_grades) / len(all_grades)
print(f"Excellent performance! Average: {average:.1f}")
case [*all_grades] if len(all_grades) >= 5:
average = sum(all_grades) / len(all_grades)
print(f"Performance review: {len(all_grades)} grades, Average: {average:.1f}")
case [*all_grades]:
print(f"Insufficient data: only {len(all_grades)} grade(s)")Output:
Performance review: 5 grades, Average: 87.6[*all_grades] 패턴은 리스트의 모든 원소를 캡처하며, 가드는 길이를 확인하고 평균을 계산해 적절한 메시지를 결정합니다.
match와 case를 사용하는 구조적 패턴 매칭은 Python에서 복잡한 의사결정을 처리하기 위한 강력하고 표현력 있는 방법을 제공합니다. 단순한 값 매칭부터 가드가 포함된 정교한 구조적 패턴까지, 이 기능은 여러 경우를 처리하고 복잡한 구조에서 데이터를 추출하는 더 깔끔하고 유지보수하기 쉬운 코드를 작성할 수 있게 해줍니다.
Python 학습을 계속하다 보면, 패턴 매칭이 8장에서 배운 조건 로직을 보완하며, 여러 개의 뚜렷한 case를 다룰 때(특히 구조화된 데이터로 작업할 때) 우아한 대안을 제공한다는 것을 알게 될 것입니다. 핵심은 각 상황에 맞는 올바른 도구를 선택하는 것입니다. 단순 조건과 불리언 로직에는 if-elif-else를 사용하고, 하나의 값을 여러 가능성과 비교하거나 패턴 기반 추출이 필요한 구조화된 데이터를 다룰 때는 match-case를 활용하세요.