Structural Pattern Matching

Python 3.10부터 도입된 구조적 패턴 매칭(Structural Pattern Matching) 은 데이터의 구조와 값을 기반으로 코드의 흐름을 제어하는 강력한 기능이다.
이는 기존의 if-elif-else 문을 대체하거나 보완하여 코드의 가독성과 유지보수성을 향상시키는 데 유용하다.

기본 개념 및 문법

구조적 패턴 매칭은 match 문과 case 절을 사용하여 구현된다.
match 문은 주어진 표현식을 평가하고, 각 case 절은 해당 표현식의 결과와 비교할 패턴을 정의한다.
가장 먼저 일치하는 패턴의 코드 블록이 실행된다.

1
2
3
4
5
6
7
match 표현식:
    case 패턴1:
        # 패턴1과 일치할 때 실행할 코드
    case 패턴2:
        # 패턴2와 일치할 때 실행할 코드
    case _:
        # 어떤 패턴과도 일치하지 않을 때 실행할 코드

여기서 case _:는 와일드카드 패턴으로, 앞의 어떤 패턴과도 일치하지 않을 때 실행된다.

간단한 예제:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def check_status(status):
    match status:
        case "online":
            print("사용자가 온라인 상태입니다.")
        case "offline":
            print("사용자가 오프라인 상태입니다.")
        case "away":
            print("사용자가 자리를 비웠습니다.")
        case _:
            print("알 수 없는 상태입니다.")

check_status("online")  # 출력: 사용자가 온라인 상태입니다.

특징

  1. 다양한 패턴 매칭: 단순한 값 비교부터 복잡한 데이터 구조까지 매칭 가능.
  2. 구조 분해: 객체나 데이터 구조를 분해하여 내부 값을 추출할 수 있음.
  3. 가드 조건case 문에 if 조건을 추가하여 더 복잡한 매칭 로직 구현 가능.
  4. OR 패턴| 연산자를 사용하여 여러 패턴을 하나의 case에서 처리 가능.
  5. 타입 매칭: 객체의 타입을 기반으로 매칭 가능.

활용 예시

OR 연산자를 사용한 다중 패턴 매칭: 여러 패턴 중 하나와 일치하면 코드를 실행한다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def access(user):
	match user:
		case "admin" | "manager":
			return "전체 접근 권한"
		case "guest":
			return "제한된 접근 권한"
		case _:
			return "접근 권한 없음"

print(access("manager"))  # 출력: 전체 접근 권한

조건부 매칭 사용:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
def categorize_score(score):
	match score:
		case score if score >= 90:
			return "A"
		case score if score >= 80:
			return "B"
		case score if score >= 70:
			return "C"
		case score if score >= 60:
			return "D"
		case _:
			return "F"

print(categorize_score(85))  # 출력: B

기본 값 매칭

1
2
3
4
5
6
7
8
def process_command(command):
    match command:
        case "start":
            return "시작합니다."
        case "stop":
            return "중지합니다."
        case _:
            return "알 수 없는 명령입니다."

구조 분해와 타입 매칭

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def process_data(data):
    match data:
        case (x, y):
            return f"좌표: ({x}, {y})"
        case [str(name), int(age)]:
            return f"이름: {name}, 나이: {age}"
        case {"name": str(name), "age": int(age)}:
            return f"이름: {name}, 나이: {age}"
        case _:
            return "알 수 없는 데이터 형식"

시퀀스 패턴 매칭:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def process_coordinates(points):
    match points:
        case []:
            print("빈 좌표 리스트")
        case [x, y]:
            print(f"2차원 좌표: ({x}, {y})")
        case [x, y, z]:
            print(f"3차원 좌표: ({x}, {y}, {z})")
        case [x, y, *rest]:
            print(f"2차원 이상의 좌표: 처음 두 값은 ({x}, {y}), 나머지: {rest}")

클래스 패턴 매칭:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class User:
    def __init__(self, username, role, active=True):
        self.username = username
        self.role = role
        self.active = active

def process_user(user):
    match user:
        case User(username=name, role="admin", active=True):
            print(f"활성 관리자: {name}")
        case User(username=name, role="user", active=True):
            print(f"활성 사용자: {name}")
        case User(username=name, active=False):
            print(f"비활성 계정: {name}")
        case _:
            print("알 수 없는 사용자 유형")

복합 패턴의 예제:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
def analyze_data_structure(data):
    match data:
        # 리스트 내부의 딕셔너리 매칭
        case [{"type": "point", "x": int(x), "y": int(y)} as point, *rest]:
            print(f"첫 번째 요소는 점 좌표: {point}")
            if rest:
                print(f"나머지 요소들: {rest}")
                
        # 중첩된 구조 매칭
        case {"data": [first, *middle, last], "type": str(type_name)}:
            print(f"타입: {type_name}")
            print(f"첫 번째: {first}, 마지막: {last}")
            print(f"중간 요소들: {middle}")
            
        # OR 패턴과 가드 조건 결합
        case str(value) | int(value) as v if v > 0:
            print(f"양수 값: {value}")
            
        case _:
            print("매칭되는 패턴이 없습니다")

실제 사용 사례를 통한 구조적 패턴 매칭의 활용:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def process_command(command):
    match command.split():
        # 파일 관련 명령어 처리
        case ["file", "create", name]:
            print(f"파일 생성: {name}")
        case ["file", "delete", name]:
            print(f"파일 삭제: {name}")
        case ["file", "move", source, dest]:
            print(f"{source}{dest}로 이동")
            
        # 사용자 관련 명령어 처리
        case ["user", "add", username, *roles]:
            print(f"사용자 추가: {username}")
            if roles:
                print(f"할당된 역할: {roles}")
        case ["user", "remove", username]:
            print(f"사용자 제거: {username}")
            
        # 기본 명령어 처리
        case ["help"]:
            print("사용 가능한 명령어 목록…")
        case ["exit" | "quit"]:
            print("프로그램 종료")
            
        case _:
            print("알 수 없는 명령어")

고급 패턴 매칭 기법:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
def analyze_complex_data(data):
    match data:
        # 딕셔너리 부분 매칭
        case {"user": {"name": str(name), **rest}}:
            print(f"사용자 이름: {name}")
            print(f"추가 정보: {rest}")
            
        # 중첩된 시퀀스 매칭
        case [[x, y] as point, *rest]:
            print(f"첫 번째 점: {point}")
            for p in rest:
                print(f"추가 점: {p}")
                
        # 복합 조건 매칭
        case {"type": type_name, "value": value} if type_name in ["int", "float"] and value > 0:
            print(f"양수 {type_name}: {value}")

가드 조건 사용

패턴 매칭에 추가적인 조건을 부여할 때 사용

1
2
3
4
5
6
def categorize_number(num):
    match num:
        case n if n  0 and n % 2 == 0:
            return "양의 짝수"
        case n if n > 0:
            return "양의 홀수"

활용 시 주의사항

  • Python 3.10 이상에서만 사용 가능: 구조적 패턴 매칭은 Python 3.10 버전부터 도입되었으므로, 해당 버전 이상에서만 사용할 수 있다.
  • 가독성 고려: 복잡한 패턴 매칭은 코드의 가독성을 떨어뜨릴 수 있으므로, 적절하게 사용해야 한다.
  • 패턴 매칭은 위에서 아래로 순차적으로 평가되며, 첫 번째 일치하는 패턴에서 실행이 멈춘다.

최적화된 사용을 위한 팁

  1. 패턴의 순서가 중요하다. 더 구체적인 패턴을 먼저 배치한다.
  2. 가드 조건은 패턴 매칭이 성공한 후에만 평가된다.
  3. 변수 캡처와 리터럴 매칭을 적절히 조합하여 사용한다.
  4. as 키워드를 사용하여 매칭된 전체 값을 참조할 수 있다.

참고 및 출처