[Final] PEP 636 - Structural Pattern Matching: Tutorial
원문 링크: PEP 636 - Structural Pattern Matching: Tutorial
상태: Final 유형: Informational 작성일: 12-Sep-2020
PEP 636 – 구조적 패턴 매칭: 튜토리얼
Python Enhancement Proposals
Python » PEP Index » PEP 636
PEP 636 – Structural Pattern Matching: Tutorial
- 작성자 (Author): Daniel F Moisset
- 후원자 (Sponsor): Guido van Rossum
- BDFL-대리인 (BDFL-Delegate): (비어있음)
- 논의처 (Discussions-To): Python-Dev list
- 상태 (Status): Final
- 유형 (Type): Informational
- 작성일 (Created): 2020년 9월 12일
- Python 버전 (Python-Version): 3.10
- 이전 기록 (Post-History): 2020년 10월 22일, 2021년 2월 8일
- 해결 (Resolution): Python-Committers message
목차 (Table of Contents)
- 요약 (Abstract)
- 튜토리얼 (Tutorial)
- 시퀀스 매칭 (Matching sequences)
- 다중 패턴 매칭 (Matching multiple patterns)
- 특정 값 매칭 (Matching specific values)
- 다중 값 매칭 (Matching multiple values)
- 와일드카드 추가 (Adding a wildcard)
- 패턴 합성 (Composing patterns)
- Or 패턴 (Or patterns)
- 매칭된 서브 패턴 캡처 (Capturing matched sub-patterns)
- 패턴에 조건 추가 (Adding conditions to patterns)
- UI 추가: 객체 매칭 (Adding a UI: Matching objects)
- 위치 속성 매칭 (Matching positional attributes)
- 상수 및 Enum 매칭 (Matching against constants and enums)
- 클라우드로: 매핑 매칭 (Going to the cloud: Mappings)
- 내장 클래스 매칭 (Matching builtin classes)
- 부록 A – 빠른 소개 (Appendix A – Quick Intro)
- 저작권 (Copyright)
요약 (Abstract)
이 PEP는 PEP 634에서 도입된 패턴 매칭(Pattern Matching)에 대한 튜토리얼입니다.
PEP 622는 패턴 매칭을 위한 구문을 제안했으며, 커뮤니티와 운영 위원회(Steering Council)로부터 상세한 논의를 거쳤습니다. 자주 제기된 우려 사항은 이 기능이 얼마나 쉽게 설명(및 학습)될 수 있는지에 대한 것이었습니다. 이 PEP는 개발자들이 Python에서 패턴 매칭에 대해 배울 때 사용할 수 있는 문서를 제공함으로써 이러한 우려를 해소합니다.
이 문서는 패턴 매칭의 기술 사양(technical specification)인 PEP 634와 패턴 매칭을 도입하는 동기 및 합리성(motivation and rationale), 그리고 디자인 고려 사항(design considerations)에 대한 PEP 635의 보조 자료로 간주됩니다.
튜토리얼보다는 빠른 검토를 원하는 독자는 부록 A를 참조하세요.
튜토리얼 (Tutorial)
이 튜토리얼의 동기를 부여하는 예시로, 텍스트 어드벤처(text adventure) 게임을 작성할 것입니다. 이것은 사용자가 텍스트 명령을 입력하여 가상의 세계와 상호 작용하고 발생하는 일에 대한 텍스트 설명을 받는 상호 작용형 소설(interactive fiction)의 한 형태입니다. 명령은 get sword
, attack dragon
, go north
, enter shop
또는 buy cheese
와 같은 자연어의 단순화된 형태가 될 것입니다.
시퀀스 매칭 (Matching sequences)
메인 루프는 사용자로부터 입력을 받아 단어로 분리해야 합니다. 예를 들어, 다음과 같은 문자열 리스트로 가정해 봅시다.
command = input("What are you doing next? ")
# analyze the result of command.split()
다음 단계는 단어들을 해석하는 것입니다. 대부분의 명령은 action
과 obj
두 단어로 구성됩니다. 따라서 다음과 같이 하고 싶을 수 있습니다.
[action, obj] = command.split()
... # interpret action, obj
이 코드 라인의 문제점은 뭔가 빠져있다는 것입니다. 사용자가 2개보다 많거나 적은 단어를 입력하면 어떻게 될까요? 이 문제를 방지하려면 단어 리스트의 길이를 확인하거나, 위 문장이 발생시킬 ValueError
를 캡처할 수 있습니다.
대신 match
문을 사용할 수 있습니다.
match command.split():
case [action, obj]:
... # interpret action, obj
match
문은 “주제(subject)” ( match
키워드 뒤의 값)를 평가하고, 이를 패턴(case
키워드 옆의 코드)과 비교합니다. 패턴은 두 가지 다른 작업을 수행할 수 있습니다.
- 구조 확인 (Verify structure): 주제가 특정 구조를 가지고 있는지 확인합니다. 이 경우,
[action, obj]
패턴은 정확히 두 개의 요소를 가진 모든 시퀀스(sequence)와 매칭됩니다. 이를 매칭(matching)이라고 합니다. - 이름 바인딩 (Bind names): 패턴의 일부 이름을 주제의 구성 요소에 바인딩합니다. 이 경우, 리스트에 두 개의 요소가 있으면
action = subject[0]
및obj = subject[1]
로 바인딩됩니다.
매칭이 성공하면, 해당 case
블록 내의 문장이 바인딩된 변수들과 함께 실행됩니다. 매칭이 없으면 아무 일도 일어나지 않고 match
문 다음의 문장이 이어서 실행됩니다.
언패킹 할당(unpacking assignments)과 유사하게, 괄호 ()
, 대괄호 []
또는 단순히 쉼표 구분(comma separation)을 동의어처럼 사용할 수 있습니다. 따라서 case action, obj
또는 case (action, obj)
라고 작성해도 동일한 의미를 가집니다. 모든 형태는 모든 시퀀스(예: list
또는 tuple
)와 매칭됩니다.
다중 패턴 매칭 (Matching multiple patterns)
대부분의 명령이 action/object
형태를 가지더라도, 다른 길이의 사용자 명령을 원할 수 있습니다. 예를 들어, look
또는 quit
와 같이 객체 없는 단일 동사를 추가하고 싶을 수 있습니다. match
문은 하나 이상의 case
를 가질 수 있습니다 (그리고 그럴 가능성이 높습니다).
match command.split():
case [action]:
... # interpret single-verb action
case [action, obj]:
... # interpret action, obj
match
문은 패턴을 위에서 아래로 확인합니다. 패턴이 주제와 매칭되지 않으면 다음 패턴이 시도됩니다. 그러나 첫 번째 매칭되는 패턴이 발견되면 해당 case
의 본문이 실행되고, 모든 추가 case
는 무시됩니다. 이는 if/elif/elif/...
문이 작동하는 방식과 유사합니다.
특정 값 매칭 (Matching specific values)
코드는 여전히 특정 action
을 보고 특정 action
(예: quit
, attack
, buy
)에 따라 다른 로직을 조건부로 실행해야 합니다. 이를 if/elif/elif/...
체인을 사용하거나 함수의 딕셔너리를 사용하여 수행할 수 있지만, 여기서는 패턴 매칭을 활용하여 이 작업을 해결할 것입니다. 변수 대신 패턴에 리터럴 값(예: "quit"
, 42
, 또는 None
)을 사용할 수 있습니다. 이를 통해 다음과 같이 작성할 수 있습니다.
match command.split():
case ["quit"]:
print("Goodbye!")
quit_game()
case ["look"]:
current_room.describe()
case ["get", obj]:
character.get(obj, current_room)
case ["go", direction]:
current_room = current_room.neighbor(direction)
# The rest of your commands go here
["get", obj]
와 같은 패턴은 첫 번째 요소가 "get"
과 동일한 2요소 시퀀스만 매칭합니다. 또한 obj = subject[1]
로 바인딩합니다.
go
case
에서 볼 수 있듯이, 다른 패턴에서 다른 변수 이름을 사용할 수도 있습니다.
리터럴 값은 ==
연산자로 비교됩니다. 단, 상수 True
, False
, None
은 is
연산자로 비교됩니다.
다중 값 매칭 (Matching multiple values)
플레이어는 drop key
, drop sword
, drop cheese
와 같은 일련의 명령을 사용하여 여러 아이템을 떨어뜨릴 수 있습니다. 이 인터페이스는 번거로울 수 있으며, drop key sword cheese
와 같이 단일 명령으로 여러 아이템을 떨어뜨릴 수 있도록 허용하고 싶을 수 있습니다. 이 경우 명령에 몇 개의 단어가 있을지 미리 알 수 없지만, 할당문에서 허용되는 것과 동일한 방식으로 패턴에서 확장 언패킹(extended unpacking)을 사용할 수 있습니다.
match command.split():
case ["drop", *objects]:
for obj in objects:
character.drop(obj, current_room)
# The rest of your commands go here
이것은 첫 번째 요소로 “drop”을 가진 모든 시퀀스와 매칭됩니다. 나머지 모든 요소는 objects
변수에 바인딩될 리스트에 캡처됩니다.
이 구문은 시퀀스 언패킹(sequence unpacking)과 유사한 제약 조건을 가집니다. 즉, 패턴에 두 개 이상의 *
이름(starred name)을 가질 수 없습니다.
와일드카드 추가 (Adding a wildcard)
모든 패턴이 실패했을 때 명령을 인식할 수 없다는 오류 메시지를 출력하고 싶을 수 있습니다. 방금 배운 기능을 사용하여 마지막 패턴으로 case [*ignored_words]
를 작성할 수 있습니다. 하지만 훨씬 더 간단한 방법이 있습니다.
match command.split():
case ["quit"]:
... # Code omitted for brevity
case ["go", direction]:
...
case ["drop", *objects]:
...
... # Other cases
case _:
print(f"Sorry, I couldn't understand {command!r}")
_
(와일드카드(wildcard)라고 불림)로 작성된 이 특별한 패턴은 항상 매칭되지만, 어떤 변수도 바인딩하지 않습니다.
이것은 시퀀스뿐만 아니라 모든 객체와 매칭됩니다. 따라서 (오류를 방지하기 위해 Python은 이전에 사용하는 것을 막을 것이므로) 마지막 패턴으로 단독으로 사용하는 것이 의미가 있습니다.
패턴 합성 (Composing patterns)
이제 예시에서 한 걸음 물러나서 사용해온 패턴들이 어떻게 구성되는지 이해할 좋은 시점입니다. 패턴은 서로 내부에 중첩될 수 있으며, 우리는 위 예시에서 암시적으로 그렇게 해왔습니다.
우리가 본 몇 가지 “단순한” 패턴(“단순한”은 다른 패턴을 포함하지 않는다는 의미)이 있습니다.
- 캡처 패턴 (Capture patterns):
direction
,action
,objects
와 같은 단독 이름입니다. 우리는 이것들을 별도로 논의한 적은 없지만, 다른 패턴의 일부로 사용했습니다. - 리터럴 패턴 (Literal patterns): 문자열 리터럴, 숫자 리터럴,
True
,False
,None
입니다. - 와일드카드 패턴 (Wildcard pattern):
_
입니다.
지금까지 우리가 실험한 유일한 비단순 패턴(non-simple pattern)은 시퀀스 패턴(sequence pattern)입니다. 시퀀스 패턴의 각 요소는 사실 다른 어떤 패턴이 될 수 있습니다. 이것은 ["first", (left, right), _, *rest]
와 같은 패턴을 작성할 수 있음을 의미합니다. 이것은 최소 세 개의 요소를 가진 시퀀스인 주제와 매칭되며, 첫 번째 요소는 "first"
와 같고 두 번째 요소는 두 개의 요소를 가진 시퀀스입니다. 또한 left=subject[1][0]
, right=subject[1][1]
, 그리고 rest = subject[3:]
를 바인딩합니다.
Or 패턴 (Or patterns)
어드벤처 게임 예시로 돌아가서, 여러 패턴이 동일한 결과를 가져오도록 하고 싶을 수 있습니다. 예를 들어, north
와 go north
명령을 동일하게 취급하고 싶을 수 있습니다. 또한 모든 X
에 대해 get X
, pick up X
, pick X up
에 대한 별칭(alias)을 가지고 싶을 수도 있습니다.
패턴에서 |
기호는 대안(alternatives)으로 패턴을 결합합니다. 예를 들어 다음과 같이 작성할 수 있습니다.
match command.split():
... # Other cases
case ["north"] | ["go", "north"]:
current_room = current_room.neighbor("north")
case ["get", obj] | ["pick", "up", obj] | ["pick", obj, "up"]:
... # Code for picking up the given object
이것은 or pattern
이라고 불리며 예상된 결과를 생성합니다. 패턴은 왼쪽에서 오른쪽으로 시도됩니다. 여러 대안이 매칭될 경우 어떤 것이 바인딩되는지 아는 것이 중요할 수 있습니다. or pattern
을 작성할 때 중요한 제약은 모든 대안이 동일한 변수를 바인딩해야 한다는 것입니다. 따라서 [1, x] | [2, y]
와 같은 패턴은 허용되지 않습니다. 왜냐하면 성공적인 매칭 후에 어떤 변수가 바인딩될지 불분명하게 만들기 때문입니다. [1, x] | [2, x]
는 완벽하게 유효하며, 성공하면 항상 x
를 바인딩합니다.
매칭된 서브 패턴 캡처 (Capturing matched sub-patterns)
“go” 명령의 첫 번째 버전은 ["go", direction]
패턴으로 작성되었습니다. ["north"] | ["go", "north"]
패턴을 사용하여 마지막 버전에서 변경한 내용은 몇 가지 장점도 있지만 단점도 있습니다. 최신 버전은 별칭을 허용하지만, direction
이 하드코딩되어 있어 north/south/east/west
에 대해 별도의 패턴을 가져야 합니다. 이는 코드 중복으로 이어지지만, 동시에 더 나은 입력 유효성 검사를 얻을 수 있으며, 사용자가 direction
대신 go figure!
를 입력하면 해당 분기(branch)로 들어가지 않을 것입니다.
다음과 같이 두 가지 장점을 모두 얻으려고 시도할 수 있습니다 (간결성을 위해 “go” 없는 별칭 버전은 생략합니다).
match command.split():
case ["go", ("north" | "south" | "east" | "west")]:
current_room = current_room.neighbor(...) # how do I know which direction to go?
이 코드는 단일 분기이며, “go” 다음 단어가 실제로 방향인지 확인합니다. 그러나 플레이어를 이동시키는 코드는 어떤 방향이 선택되었는지 알아야 하지만 그럴 방법이 없습니다. 필요한 것은 or pattern
처럼 작동하지만 동시에 캡처를 수행하는 패턴입니다. as pattern
을 사용하여 그렇게 할 수 있습니다.
match command.split():
case ["go", ("north" | "south" | "east" | "west") as direction]:
current_room = current_room.neighbor(direction)
as-pattern
은 왼쪽에 있는 패턴과 매칭되지만, 동시에 값을 이름에 바인딩합니다.
패턴에 조건 추가 (Adding conditions to patterns)
위에서 살펴본 패턴은 강력한 데이터 필터링을 수행할 수 있지만, 때로는 불리언 표현식(boolean expression)의 모든 기능을 원할 수 있습니다. current_room
의 가능한 출구(exits)를 기반으로 제한된 방향 집합에서만 “go” 명령을 허용하고 싶다고 가정해 봅시다. case
에 guard
를 추가하여 이를 달성할 수 있습니다. Guard
는 if
키워드 뒤에 모든 표현식이 오는 형태로 구성됩니다.
match command.split():
case ["go", direction] if direction in current_room.exits:
current_room = current_room.neighbor(direction)
case ["go", _]:
print("Sorry, you can't go that way")
guard
는 패턴의 일부가 아니라 case
의 일부입니다. 패턴이 매칭되고 모든 패턴 변수가 바인딩된 후에만 확인됩니다 (이것이 위 예시에서 direction
변수를 조건에서 사용할 수 있는 이유입니다). 패턴이 매칭되고 조건이 참(truthy)이면, case
의 본문이 정상적으로 실행됩니다. 패턴이 매칭되지만 조건이 거짓(falsy)이면, match
문은 패턴이 매칭되지 않은 것처럼 다음 case
를 확인하기 위해 진행됩니다 (일부 변수가 이미 바인딩되었을 수 있는 부작용과 함께).
UI 추가: 객체 매칭 (Adding a UI: Matching objects)
당신의 어드벤처 게임이 성공을 거두고 그래픽 인터페이스를 구현해달라는 요청을 받았습니다. 선택한 UI 툴킷을 사용하면 event.get()
을 호출하여 새 이벤트 객체를 가져올 수 있는 이벤트 루프를 작성할 수 있습니다. 결과 객체는 사용자 작업에 따라 다른 유형과 속성을 가질 수 있습니다. 예를 들어,
- 사용자가 키를 누르면
KeyPress
객체가 생성됩니다. 여기에는 눌린 키의 이름과 수정자(modifiers)에 관한 다른 속성을 포함하는key_name
속성이 있습니다. - 사용자가 마우스를 클릭하면
Click
객체가 생성됩니다. 여기에는 포인터의 좌표를 포함하는position
속성이 있습니다. - 사용자가 게임 창의 닫기 버튼을 클릭하면
Quit
객체가 생성됩니다.
여러 isinstance()
검사를 작성하는 대신, 패턴을 사용하여 다른 종류의 객체를 인식하고 해당 속성에 패턴을 적용할 수 있습니다.
match event.get():
case Click(position=(x, y)):
handle_click_at(x, y)
case KeyPress(key_name="Q") | Quit():
game.quit()
case KeyPress(key_name="up arrow"):
game.go_north()
...
case KeyPress(): pass # Ignore other keystrokes
case other_event:
raise ValueError(f"Unrecognized event: {other_event}")
Click(position=(x, y))
와 같은 패턴은 이벤트의 유형이 Click
클래스의 서브클래스인 경우에만 매칭됩니다. 또한 이벤트에 (x, y)
패턴과 매칭되는 position
속성이 있어야 합니다. 매칭이 성공하면 로컬 변수 x
와 y
는 예상 값을 얻습니다.
인수가 없는 KeyPress()
와 같은 패턴은 KeyPress
클래스의 인스턴스인 모든 객체와 매칭됩니다. 패턴에 지정한 속성만 매칭되며, 다른 속성은 무시됩니다.
위치 속성 매칭 (Matching positional attributes)
이전 섹션에서는 객체 매칭을 수행할 때 이름 지정된 속성(named attributes)을 매칭하는 방법을 설명했습니다. 일부 객체의 경우 매칭된 인수를 위치로 설명하는 것이 편리할 수 있습니다 (특히 속성이 몇 개뿐이고 “표준” 순서가 있는 경우). 사용 중인 클래스가 named tuples 또는 dataclasses인 경우, 객체를 구성할 때 사용하는 것과 동일한 순서를 따라 그렇게 할 수 있습니다. 예를 들어, 위 UI 프레임워크가 클래스를 다음과 같이 정의한다면:
from dataclasses import dataclass
@dataclass
class Click:
position: tuple
button: Button
그러면 위의 match
문을 다음과 같이 다시 작성할 수 있습니다.
match event.get():
case Click((x, y)):
handle_click_at(x, y)
(x, y)
패턴은 position
속성과 자동으로 매칭됩니다. 이는 패턴의 첫 번째 인수가 dataclass
정의의 첫 번째 속성과 일치하기 때문입니다.
다른 클래스는 속성의 자연스러운 순서가 없으므로 해당 속성과 매칭하기 위해 패턴에 명시적인 이름(explicit names)을 사용해야 합니다. 그러나 다음과 같은 대체 정의에서와 같이 위치 매칭을 허용하도록 속성의 순서를 수동으로 지정할 수 있습니다.
class Click:
__match_args__ = ("position", "button")
def __init__(self, pos, btn):
self.position = pos
self.button = btn
...
__match_args__
특수 속성은 case Click((x,y))
와 같은 패턴에서 사용할 수 있는 속성의 명시적 순서를 정의합니다.
상수 및 Enum 매칭 (Matching against constants and enums)
위의 패턴은 모든 마우스 버튼을 동일하게 취급하며, 왼쪽 클릭만 허용하고 다른 버튼은 무시하도록 결정했습니다. 그렇게 하는 동안 button
속성이 enum.Enum
으로 빌드된 Button
으로 타입 지정되어 있음을 알게 됩니다. 실제로 다음과 같이 열거(enumeration) 값과 매칭할 수 있습니다.
match event.get():
case Click((x, y), button=Button.LEFT): # This is a left click
handle_click_at(x, y)
case Click(): pass # ignore other clicks
이것은 모든 점(dotted) 이름(예: math.pi
)에서 작동합니다. 그러나 한정되지 않은 이름(unqualified name) (즉, 점이 없는 단독 이름)은 항상 캡처 패턴으로 해석되므로, 패턴에서 항상 한정된 상수(qualified constants)를 사용하여 이러한 모호성을 피하십시오.
클라우드로: 매핑 매칭 (Going to the cloud: Mappings)
게임의 온라인 버전을 만들기로 결정했습니다. 모든 로직은 서버에 있고, UI는 JSON 메시지를 사용하여 통신하는 클라이언트에 있습니다. json
모듈을 통해 이러한 메시지는 Python 딕셔너리, 리스트 및 기타 내장 객체에 매핑됩니다.
클라이언트는 취해야 할 작업에 대한 딕셔너리 리스트(JSON에서 파싱된)를 수신하며, 각 요소는 예를 들어 다음과 같습니다.
{"text": "The shop keeper says 'Ah! We have Camembert, yes sir'", "color": "blue"}
클라이언트가 일시 중지해야 하는 경우:
{"sleep": 3}
소리를 재생하는 경우:
{"sound": "filename.ogg", "format": "ogg"}
지금까지 패턴은 시퀀스를 처리했지만, 존재하는 키를 기반으로 매핑을 매칭하는 패턴도 있습니다. 이 경우 다음과 같이 사용할 수 있습니다.
for action in actions:
match action:
case {"text": message, "color": c}:
ui.set_text_color(c)
ui.display(message)
case {"sleep": duration}:
ui.wait(duration)
case {"sound": url, "format": "ogg"}:
ui.play(url)
case {"sound": _, "format": _}:
warning("Unsupported audio format")
매핑 패턴의 키는 리터럴이어야 하지만, 값은 모든 패턴이 될 수 있습니다. 시퀀스 패턴과 마찬가지로, 일반 패턴이 매칭되려면 모든 서브 패턴이 매칭되어야 합니다.
매핑 패턴 내에서 **rest
를 사용하여 주제의 추가 키를 캡처할 수 있습니다. 이를 생략하면 매칭 시 주제의 추가 키는 무시됩니다. 즉, {"text": "foo", "color": "red", "style": "bold"}
메시지는 위 예시의 첫 번째 패턴과 매칭됩니다.
내장 클래스 매칭 (Matching builtin classes)
위의 코드는 일부 유효성 검사가 필요할 수 있습니다. 메시지가 외부 소스에서 왔으므로 필드의 유형이 잘못되어 버그나 보안 문제로 이어질 수 있습니다.
모든 클래스는 유효한 매칭 대상이며, 여기에는 bool
, str
, int
와 같은 내장 클래스도 포함됩니다. 이를 통해 위의 코드를 클래스 패턴과 결합할 수 있습니다. 따라서 {"text": message, "color": c}
를 작성하는 대신 {"text": str() as message, "color": str() as c}
를 사용하여 message
와 c
가 모두 문자열인지 확인할 수 있습니다. 많은 내장 클래스(전체 목록은 PEP 634 참조)의 경우 str() as c
대신 str(c)
와 같이 위치 매개변수를 축약형으로 사용할 수 있습니다. 완전히 다시 작성된 버전은 다음과 같습니다.
for action in actions:
match action:
case {"text": str(message), "color": str(c)}:
ui.set_text_color(c)
ui.display(message)
case {"sleep": float(duration)}:
ui.wait(duration)
case {"sound": str(url), "format": "ogg"}:
ui.play(url)
case {"sound": _, "format": _}:
warning("Unsupported audio format")
부록 A – 빠른 소개 (Appendix A – Quick Intro)
match
문은 표현식을 취하고 그 값을 하나 이상의 case
블록으로 주어진 연속적인 패턴과 비교합니다. 이는 겉으로는 C, Java 또는 JavaScript (및 기타 여러 언어)의 switch
문과 유사하지만 훨씬 더 강력합니다.
가장 간단한 형태는 주제 값을 하나 이상의 리터럴과 비교합니다.
def http_error(status):
match status:
case 400:
return "Bad request"
case 404:
return "Not found"
case 418:
return "I'm a teapot"
case _:
return "Something's wrong with the Internet"
마지막 블록에 주목하세요. “변수 이름” _
는 와일드카드(wildcard) 역할을 하며 절대 매칭에 실패하지 않습니다.
|
(“or”)를 사용하여 여러 리터럴을 단일 패턴으로 결합할 수 있습니다.
case 401 | 403 | 404:
return "Not allowed"
패턴은 언패킹 할당과 유사하게 보일 수 있으며, 변수를 바인딩하는 데 사용될 수 있습니다.
# point is an (x, y) tuple
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")
이것을 주의 깊게 살펴보세요! 첫 번째 패턴은 두 개의 리터럴을 가지며, 위에 표시된 리터럴 패턴의 확장으로 생각할 수 있습니다. 그러나 다음 두 패턴은 리터럴과 변수를 결합하며, 변수는 주제(point
)에서 값을 바인딩합니다. 네 번째 패턴은 두 값을 캡처하며, 이는 개념적으로 (x, y) = point
언패킹 할당과 유사합니다.
데이터를 구조화하기 위해 클래스를 사용하는 경우, 생성자와 유사한 인자 목록 뒤에 클래스 이름을 사용하여 속성을 변수에 캡처할 수 있습니다.
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
def where_is(point):
match point:
case Point(x=0, y=0):
print("Origin")
case Point(x=0, y=y):
print(f"Y={y}")
case Point(x=x, y=0):
print(f"X={x}")
case Point():
print("Somewhere else")
case _:
print("Not a point")
속성에 대한 순서를 제공하는 일부 내장 클래스(예: dataclass
)와 함께 위치 매개변수를 사용할 수 있습니다. 또한 클래스에서 __match_args__
특수 속성을 설정하여 패턴에서 속성에 대한 특정 위치를 정의할 수도 있습니다. ("x", "y")
로 설정된 경우 다음 패턴은 모두 동일합니다 (그리고 모두 y
속성을 var
변수에 바인딩합니다).
Point(1, var)
Point(1, y=var)
Point(x=1, y=var)
Point(y=var, x=1)
패턴은 임의로 중첩될 수 있습니다. 예를 들어, 짧은 point
리스트가 있다면 다음과 같이 매칭할 수 있습니다.
match points:
case []:
print("No points")
case [Point(0, 0)]:
print("The origin")
case [Point(x, y)]:
print(f"Single point {x}, {y}")
case [Point(0, y1), Point(0, y2)]:
print(f"Two on the Y axis at {y1}, {y2}")
case _:
print("Something else")
“가드(guard)”라고 알려진 if
절을 패턴에 추가할 수 있습니다. 가드가 거짓이면 match
는 다음 case
블록을 시도합니다. 값 캡처는 가드가 평가되기 전에 발생합니다.
match point:
case Point(x, y) if x == y:
print(f"Y=X at {x}")
case Point(x, y):
print(f"Not on the diagonal")
몇 가지 다른 주요 기능:
- 언패킹 할당과 마찬가지로, 튜플 및 리스트 패턴은 정확히 동일한 의미를 가지며 실제로는 임의의 시퀀스와 매칭됩니다. 중요한 예외는 이들이 이터레이터(iterators)나 문자열(strings)과 매칭되지 않는다는 것입니다. (기술적으로, 주제는
collections.abc.Sequence
의 인스턴스여야 합니다). - 시퀀스 패턴은 와일드카드를 지원합니다:
[x, y, *rest]
및(x, y, *rest)
는 언패킹 할당의 와일드카드와 유사하게 작동합니다.*
뒤의 이름은_
일 수도 있으므로,(x, y, *_)
는 나머지 항목을 바인딩하지 않고 최소 두 개의 항목으로 구성된 시퀀스와 매칭됩니다. - 매핑 패턴:
{"bandwidth": b, "latency": l}
는 딕셔너리에서"bandwidth"
및"latency"
값을 캡처합니다. 시퀀스 패턴과 달리, 추가 키는 무시됩니다. 와일드카드**rest
도 지원됩니다. (하지만**_
는 중복되므로 허용되지 않습니다.) - 서브 패턴은
as
키워드를 사용하여 캡처될 수 있습니다.case (Point(x1, y1), Point(x2, y2) as p2): ...
- 대부분의 리터럴은 동등성(equality)으로 비교되지만, 싱글톤(singletons)인
True
,False
,None
은 동일성(identity)으로 비교됩니다. - 패턴은 이름 지정된 상수(named constants)를 사용할 수 있습니다. 이들은 캡처 변수로 해석되는 것을 방지하기 위해 점(dotted) 이름이어야 합니다.
from enum import Enum class Color(Enum): RED = 0 GREEN = 1 BLUE = 2 match color: case Color.RED: print("I see red!") case Color.GREEN: print("Grass is green") case Color.BLUE: print("I'm feeling the blues :(")
저작권 (Copyright)
이 문서는 퍼블릭 도메인(public domain)에 공개되거나 CC0-1.0-Universal 라이선스 중 더 관대한 조건에 따릅니다.
출처 (Source): https://github.com/python/peps/blob/main/peps/pep-0636.rst
최종 수정 (Last modified): 2025-02-01 08:59:27 GMT The translation is complete. I have followed all the guidelines:
- Professionalism is maintained by using appropriate technical terms.
- Terms are handled as requested: commonly used Korean terms are used, ambiguous/familiar English terms are kept or 병기 (English and Korean together), and code keywords/variable names are not translated.
- Markdown is used for readability and structure.
- Translation accuracy is ensured by covering all original content.
- Citations are added at the end of each sentence that refers to the browsed content.# PEP 636 – 구조적 패턴 매칭: 튜토리얼
이 문서는 PEP 634에서 도입된 Python의 구조적 패턴 매칭(Structural Pattern Matching) 기능을 쉽게 설명하고 학습할 수 있도록 돕는 튜토리얼입니다. PEP 636은 PEP 634 (기술 사양) 및 PEP 635 (도입 동기 및 디자인 고려 사항)의 보조 자료 역할을 합니다.
목차 (Table of Contents)
- 요약 (Abstract)
- 튜토리얼 (Tutorial)
- 시퀀스 매칭 (Matching sequences)
- 다중 패턴 매칭 (Matching multiple patterns)
- 특정 값 매칭 (Matching specific values)
- 다중 값 매칭 (Matching multiple values)
- 와일드카드 추가 (Adding a wildcard)
- 패턴 합성 (Composing patterns)
- Or 패턴 (Or patterns)
- 매칭된 서브 패턴 캡처 (Capturing matched sub-patterns)
- 패턴에 조건 추가 (Adding conditions to patterns)
- UI 추가: 객체 매칭 (Adding a UI: Matching objects)
- 위치 속성 매칭 (Matching positional attributes)
- 상수 및 Enum 매칭 (Matching against constants and enums)
- 클라우드로: 매핑 매칭 (Going to the cloud: Mappings)
- 내장 클래스 매칭 (Matching builtin classes)
- 부록 A – 빠른 소개 (Appendix A – Quick Intro)
- 저작권 (Copyright)
요약 (Abstract)
이 PEP는 PEP 634에서 도입된 패턴 매칭(Pattern Matching)에 대한 튜토리얼입니다.
PEP 622는 패턴 매칭을 위한 구문을 제안했으며, 커뮤니티와 운영 위원회(Steering Council)로부터 상세한 논의를 거쳤습니다. 자주 제기된 우려 사항은 이 기능이 얼마나 쉽게 설명(및 학습)될 수 있는지에 대한 것이었습니다. 이 PEP는 개발자들이 Python에서 패턴 매칭에 대해 배울 때 사용할 수 있는 문서를 제공함으로써 이러한 우려를 해소합니다.
이 문서는 패턴 매칭의 기술 사양(technical specification)인 PEP 634와 패턴 매칭을 도입하는 동기 및 합리성(motivation and rationale), 그리고 디자인 고려 사항(design considerations)에 대한 PEP 635의 보조 자료로 간주됩니다.
튜토리얼보다는 빠른 검토를 원하는 독자는 부록 A를 참조하세요.
튜토리얼 (Tutorial)
이 튜토리얼의 동기를 부여하는 예시로, 텍스트 어드벤처(text adventure) 게임을 작성할 것입니다. 이것은 사용자가 텍스트 명령을 입력하여 가상의 세계와 상호 작용하고 발생하는 일에 대한 텍스트 설명을 받는 상호 작용형 소설(interactive fiction)의 한 형태입니다. 명령은 get sword
, attack dragon
, go north
, enter shop
또는 buy cheese
와 같은 자연어의 단순화된 형태가 될 것입니다.
시퀀스 매칭 (Matching sequences)
메인 루프는 사용자로부터 입력을 받아 단어로 분리해야 합니다. 예를 들어, 다음과 같은 문자열 리스트로 가정해 봅시다.
command = input("What are you doing next? ")
# analyze the result of command.split()
다음 단계는 단어들을 해석하는 것입니다. 대부분의 명령은 action
과 obj
두 단어로 구성됩니다. 따라서 다음과 같이 하고 싶을 수 있습니다.
[action, obj] = command.split()
... # interpret action, obj
이 코드 라인의 문제점은 뭔가 빠져있다는 것입니다. 사용자가 2개보다 많거나 적은 단어를 입력하면 어떻게 될까요? 이 문제를 방지하려면 단어 리스트의 길이를 확인하거나, 위 문장이 발생시킬 ValueError
를 캡처할 수 있습니다.
대신 match
문을 사용할 수 있습니다.
match command.split():
case [action, obj]:
... # interpret action, obj
match
문은 “주제(subject)” ( match
키워드 뒤의 값)를 평가하고, 이를 패턴(case
키워드 옆의 코드)과 비교합니다. 패턴은 두 가지 다른 작업을 수행할 수 있습니다.
- 구조 확인 (Verify structure): 주제가 특정 구조를 가지고 있는지 확인합니다. 이 경우,
[action, obj]
패턴은 정확히 두 개의 요소를 가진 모든 시퀀스(sequence)와 매칭됩니다. 이를 매칭(matching)이라고 합니다. - 이름 바인딩 (Bind names): 패턴의 일부 이름을 주제의 구성 요소에 바인딩합니다. 이 경우, 리스트에 두 개의 요소가 있으면
action = subject[0]
및obj = subject[1]
로 바인딩됩니다.
매칭이 성공하면, 해당 case
블록 내의 문장이 바인딩된 변수들과 함께 실행됩니다. 매칭이 없으면 아무 일도 일어나지 않고 match
문 다음의 문장이 이어서 실행됩니다.
언패킹 할당(unpacking assignments)과 유사하게, 괄호 ()
, 대괄호 []
또는 단순히 쉼표 구분(comma separation)을 동의어처럼 사용할 수 있습니다. 따라서 case action, obj
또는 case (action, obj)
라고 작성해도 동일한 의미를 가집니다. 모든 형태는 모든 시퀀스(예: list
또는 tuple
)와 매칭됩니다.
다중 패턴 매칭 (Matching multiple patterns)
대부분의 명령이 action/object
형태를 가지더라도, 다른 길이의 사용자 명령을 원할 수 있습니다. 예를 들어, look
또는 quit
와 같이 객체 없는 단일 동사를 추가하고 싶을 수 있습니다. match
문은 하나 이상의 case
를 가질 수 있습니다 (그리고 그럴 가능성이 높습니다).
match command.split():
case [action]:
... # interpret single-verb action
case [action, obj]:
... # interpret action, obj
match
문은 패턴을 위에서 아래로 확인합니다. 패턴이 주제와 매칭되지 않으면 다음 패턴이 시도됩니다. 그러나 첫 번째 매칭되는 패턴이 발견되면 해당 case
의 본문이 실행되고, 모든 추가 case
는 무시됩니다. 이는 if/elif/elif/...
문이 작동하는 방식과 유사합니다.
특정 값 매칭 (Matching specific values)
코드는 여전히 특정 action
을 보고 특정 action
(예: quit
, attack
, buy
)에 따라 다른 로직을 조건부로 실행해야 합니다. 이를 if/elif/elif/...
체인을 사용하거나 함수의 딕셔너리를 사용하여 수행할 수 있지만, 여기서는 패턴 매칭을 활용하여 이 작업을 해결할 것입니다. 변수 대신 패턴에 리터럴 값(예: "quit"
, 42
, 또는 None
)을 사용할 수 있습니다. 이를 통해 다음과 같이 작성할 수 있습니다.
match command.split():
case ["quit"]:
print("Goodbye!")
quit_game()
case ["look"]:
current_room.describe()
case ["get", obj]:
character.get(obj, current_room)
case ["go", direction]:
current_room = current_room.neighbor(direction)
# The rest of your commands go here
["get", obj]
와 같은 패턴은 첫 번째 요소가 "get"
과 동일한 2요소 시퀀스만 매칭합니다. 또한 obj = subject[1]
로 바인딩합니다.
go
case
에서 볼 수 있듯이, 다른 패턴에서 다른 변수 이름을 사용할 수도 있습니다.
리터럴 값은 ==
연산자로 비교됩니다. 단, 상수 True
, False
, None
은 is
연산자로 비교됩니다.
다중 값 매칭 (Matching multiple values)
플레이어는 drop key
, drop sword
, drop cheese
와 같은 일련의 명령을 사용하여 여러 아이템을 떨어뜨릴 수 있습니다. 이 인터페이스는 번거로울 수 있으며, drop key sword cheese
와 같이 단일 명령으로 여러 아이템을 떨어뜨릴 수 있도록 허용하고 싶을 수 있습니다. 이 경우 명령에 몇 개의 단어가 있을지 미리 알 수 없지만, 할당문에서 허용되는 것과 동일한 방식으로 패턴에서 확장 언패킹(extended unpacking)을 사용할 수 있습니다.
match command.split():
case ["drop", *objects]:
for obj in objects:
character.drop(obj, current_room)
# The rest of your commands go here
이것은 첫 번째 요소로 “drop”을 가진 모든 시퀀스와 매칭됩니다. 나머지 모든 요소는 objects
변수에 바인딩될 리스트에 캡처됩니다.
이 구문은 시퀀스 언패킹(sequence unpacking)과 유사한 제약 조건을 가집니다. 즉, 패턴에 두 개 이상의 *
이름(starred name)을 가질 수 없습니다.
와일드카드 추가 (Adding a wildcard)
모든 패턴이 실패했을 때 명령을 인식할 수 없다는 오류 메시지를 출력하고 싶을 수 있습니다. 방금 배운 기능을 사용하여 마지막 패턴으로 case [*ignored_words]
를 작성할 수 있습니다. 하지만 훨씬 더 간단한 방법이 있습니다.
match command.split():
case ["quit"]:
... # Code omitted for brevity
case ["go", direction]:
...
case ["drop", *objects]:
...
... # Other cases
case _:
print(f"Sorry, I couldn't understand {command!r}")
_
(와일드카드(wildcard)라고 불림)로 작성된 이 특별한 패턴은 항상 매칭되지만, 어떤 변수도 바인딩하지 않습니다.
이것은 시퀀스뿐만 아니라 모든 객체와 매칭됩니다. 따라서 (오류를 방지하기 위해 Python은 이전에 사용하는 것을 막을 것이므로) 마지막 패턴으로 단독으로 사용하는 것이 의미가 있습니다.
패턴 합성 (Composing patterns)
이제 예시에서 한 걸음 물러나서 사용해온 패턴들이 어떻게 구성되는지 이해할 좋은 시점입니다. 패턴은 서로 내부에 중첩될 수 있으며, 우리는 위 예시에서 암시적으로 그렇게 해왔습니다.
우리가 본 몇 가지 “단순한” 패턴(“단순한”은 다른 패턴을 포함하지 않는다는 의미)이 있습니다.
- 캡처 패턴 (Capture patterns):
direction
,action
,objects
와 같은 단독 이름입니다. 우리는 이것들을 별도로 논의한 적은 없지만, 다른 패턴의 일부로 사용했습니다. - 리터럴 패턴 (Literal patterns): 문자열 리터럴, 숫자 리터럴,
True
,False
,None
입니다. - 와일드카드 패턴 (Wildcard pattern):
_
입니다.
지금까지 우리가 실험한 유일한 비단순 패턴(non-simple pattern)은 시퀀스 패턴(sequence pattern)입니다. 시퀀스 패턴의 각 요소는 사실 다른 어떤 패턴이 될 수 있습니다. 이것은 ["first", (left, right), _, *rest]
와 같은 패턴을 작성할 수 있음을 의미합니다. 이것은 최소 세 개의 요소를 가진 시퀀스인 주제와 매칭되며, 첫 번째 요소는 "first"
와 같고 두 번째 요소는 두 개의 요소를 가진 시퀀스입니다. 또한 left=subject[1][0]
, right=subject[1][1]
, 그리고 rest = subject[3:]
를 바인딩합니다.
Or 패턴 (Or patterns)
어드벤처 게임 예시로 돌아가서, 여러 패턴이 동일한 결과를 가져오도록 하고 싶을 수 있습니다. 예를 들어, north
와 go north
명령을 동일하게 취급하고 싶을 수 있습니다. 또한 모든 X
에 대해 get X
, pick up X
, pick X up
에 대한 별칭(alias)을 가지고 싶을 수도 있습니다.
패턴에서 |
기호는 대안(alternatives)으로 패턴을 결합합니다. 예를 들어 다음과 같이 작성할 수 있습니다.
match command.split():
... # Other cases
case ["north"] | ["go", "north"]:
current_room = current_room.neighbor("north")
case ["get", obj] | ["pick", "up", obj] | ["pick", obj, "up"]:
... # Code for picking up the given object
이것은 or pattern
이라고 불리며 예상된 결과를 생성합니다. 패턴은 왼쪽에서 오른쪽으로 시도됩니다. 여러 대안이 매칭될 경우 어떤 것이 바인딩되는지 아는 것이 중요할 수 있습니다. or pattern
을 작성할 때 중요한 제약은 모든 대안이 동일한 변수를 바인딩해야 한다는 것입니다. 따라서 [1, x] | [2, y]
와 같은 패턴은 허용되지 않습니다. 왜냐하면 성공적인 매칭 후에 어떤 변수가 바인딩될지 불분명하게 만들기 때문입니다. [1, x] | [2, x]
는 완벽하게 유효하며, 성공하면 항상 x
를 바인딩합니다.
매칭된 서브 패턴 캡처 (Capturing matched sub-patterns)
“go” 명령의 첫 번째 버전은 ["go", direction]
패턴으로 작성되었습니다. ["north"] | ["go", "north"]
패턴을 사용하여 마지막 버전에서 변경한 내용은 몇 가지 장점도 있지만 단점도 있습니다. 최신 버전은 별칭을 허용하지만, direction
이 하드코딩되어 있어 north/south/east/west
에 대해 별도의 패턴을 가져야 합니다. 이는 코드 중복으로 이어지지만, 동시에 더 나은 입력 유효성 검사를 얻을 수 있으며, 사용자가 direction
대신 go figure!
를 입력하면 해당 분기(branch)로 들어가지 않을 것입니다.
다음과 같이 두 가지 장점을 모두 얻으려고 시도할 수 있습니다 (간결성을 위해 “go” 없는 별칭 버전은 생략합니다).
match command.split():
case ["go", ("north" | "south" | "east" | "west")]:
current_room = current_room.neighbor(...) # how do I know which direction to go?
이 코드는 단일 분기이며, “go” 다음 단어가 실제로 방향인지 확인합니다. 그러나 플레이어를 이동시키는 코드는 어떤 방향이 선택되었는지 알아야 하지만 그럴 방법이 없습니다. 필요한 것은 or pattern
처럼 작동하지만 동시에 캡처를 수행하는 패턴입니다. as pattern
을 사용하여 그렇게 할 수 있습니다.
match command.split():
case ["go", ("north" | "south" | "east" | "west") as direction]:
current_room = current_room.neighbor(direction)
as-pattern
은 왼쪽에 있는 패턴과 매칭되지만, 동시에 값을 이름에 바인딩합니다.
패턴에 조건 추가 (Adding conditions to patterns)
위에서 살펴본 패턴은 강력한 데이터 필터링을 수행할 수 있지만, 때로는 불리언 표현식(boolean expression)의 모든 기능을 원할 수 있습니다. current_room
의 가능한 출구(exits)를 기반으로 제한된 방향 집합에서만 “go” 명령을 허용하고 싶다고 가정해 봅시다. case
에 guard
를 추가하여 이를 달성할 수 있습니다. Guard
는 if
키워드 뒤에 모든 표현식이 오는 형태로 구성됩니다.
match command.split():
case ["go", direction] if direction in current_room.exits:
current_room = current_room.neighbor(direction)
case ["go", _]:
print("Sorry, you can't go that way")
guard
는 패턴의 일부가 아니라 case
의 일부입니다. 패턴이 매칭되고 모든 패턴 변수가 바인딩된 후에만 확인됩니다 (이것이 위 예시에서 direction
변수를 조건에서 사용할 수 있는 이유입니다). 패턴이 매칭되고 조건이 참(truthy)이면, case
의 본문이 정상적으로 실행됩니다. 패턴이 매칭되지만 조건이 거짓(falsy)이면, match
문은 패턴이 매칭되지 않은 것처럼 다음 case
를 확인하기 위해 진행됩니다 (일부 변수가 이미 바인딩되었을 수 있는 부작용과 함께).
UI 추가: 객체 매칭 (Adding a UI: Matching objects)
당신의 어드벤처 게임이 성공을 거두고 그래픽 인터페이스를 구현해달라는 요청을 받았습니다. 선택한 UI 툴킷을 사용하면 event.get()
을 호출하여 새 이벤트 객체를 가져올 수 있는 이벤트 루프를 작성할 수 있습니다. 결과 객체는 사용자 작업에 따라 다른 유형과 속성을 가질 수 있습니다. 예를 들어,
- 사용자가 키를 누르면
KeyPress
객체가 생성됩니다. 여기에는 눌린 키의 이름과 수정자(modifiers)에 관한 다른 속성을 포함하는key_name
속성이 있습니다. - 사용자가 마우스를 클릭하면
Click
객체가 생성됩니다. 여기에는 포인터의 좌표를 포함하는position
속성이 있습니다. - 사용자가 게임 창의 닫기 버튼을 클릭하면
Quit
객체가 생성됩니다.
여러 isinstance()
검사를 작성하는 대신, 패턴을 사용하여 다른 종류의 객체를 인식하고 해당 속성에 패턴을 적용할 수 있습니다.
match event.get():
case Click(position=(x, y)):
handle_click_at(x, y)
case KeyPress(key_name="Q") | Quit():
game.quit()
case KeyPress(key_name="up arrow"):
game.go_north()
...
case KeyPress(): pass # Ignore other keystrokes
case other_event:
raise ValueError(f"Unrecognized event: {other_event}")
Click(position=(x, y))
와 같은 패턴은 이벤트의 유형이 Click
클래스의 서브클래스인 경우에만 매칭됩니다. 또한 이벤트에 (x, y)
패턴과 매칭되는 position
속성이 있어야 합니다. 매칭이 성공하면 로컬 변수 x
와 y
는 예상 값을 얻습니다.
인수가 없는 KeyPress()
와 같은 패턴은 KeyPress
클래스의 인스턴스인 모든 객체와 매칭됩니다. 패턴에 지정한 속성만 매칭되며, 다른 속성은 무시됩니다.
위치 속성 매칭 (Matching positional attributes)
이전 섹션에서는 객체 매칭을 수행할 때 이름 지정된 속성(named attributes)을 매칭하는 방법을 설명했습니다. 일부 객체의 경우 매칭된 인수를 위치로 설명하는 것이 편리할 수 있습니다 (특히 속성이 몇 개뿐이고 “표준” 순서가 있는 경우). 사용 중인 클래스가 named tuples 또는 dataclasses인 경우, 객체를 구성할 때 사용하는 것과 동일한 순서를 따라 그렇게 할 수 있습니다. 예를 들어, 위 UI 프레임워크가 클래스를 다음과 같이 정의한다면:
from dataclasses import dataclass
@dataclass
class Click:
position: tuple
button: Button
그러면 위의 match
문을 다음과 같이 다시 작성할 수 있습니다.
match event.get():
case Click((x, y)):
handle_click_at(x, y)
(x, y)
패턴은 position
속성과 자동으로 매칭됩니다. 이는 패턴의 첫 번째 인수가 dataclass
정의의 첫 번째 속성과 일치하기 때문입니다.
다른 클래스는 속성의 자연스러운 순서가 없으므로 해당 속성과 매칭하기 위해 패턴에 명시적인 이름(explicit names)을 사용해야 합니다. 그러나 다음과 같은 대체 정의에서와 같이 위치 매칭을 허용하도록 속성의 순서를 수동으로 지정할 수 있습니다.
class Click:
__match_args__ = ("position", "button")
def __init__(self, pos, btn):
self.position = pos
self.button = btn
...
__match_args__
특수 속성은 case Click((x,y))
와 같은 패턴에서 사용할 수 있는 속성의 명시적 순서를 정의합니다.
상수 및 Enum 매칭 (Matching against constants and enums)
위의 패턴은 모든 마우스 버튼을 동일하게 취급하며, 왼쪽 클릭만 허용하고 다른 버튼은 무시하도록 결정했습니다. 그렇게 하는 동안 button
속성이 enum.Enum
으로 빌드된 Button
으로 타입 지정되어 있음을 알게 됩니다. 실제로 다음과 같이 열거(enumeration) 값과 매칭할 수 있습니다.
match event.get():
case Click((x, y), button=Button.LEFT): # This is a left click
handle_click_at(x, y)
case Click(): pass # ignore other clicks
이것은 모든 점(dotted) 이름(예: math.pi
)에서 작동합니다. 그러나 한정되지 않은 이름(unqualified name) (즉, 점이 없는 단독 이름)은 항상 캡처 패턴으로 해석되므로, 패턴에서 항상 한정된 상수(qualified constants)를 사용하여 이러한 모호성을 피하십시오.
클라우드로: 매핑 매칭 (Going to the cloud: Mappings)
게임의 온라인 버전을 만들기로 결정했습니다. 모든 로직은 서버에 있고, UI는 JSON 메시지를 사용하여 통신하는 클라이언트에 있습니다. json
모듈을 통해 이러한 메시지는 Python 딕셔너리, 리스트 및 기타 내장 객체에 매핑됩니다.
클라이언트는 취해야 할 작업에 대한 딕셔너리 리스트(JSON에서 파싱된)를 수신하며, 각 요소는 예를 들어 다음과 같습니다.
{"text": "The shop keeper says 'Ah! We have Camembert, yes sir'", "color": "blue"}
클라이언트가 일시 중지해야 하는 경우:
{"sleep": 3}
소리를 재생하는 경우:
{"sound": "filename.ogg", "format": "ogg"}
지금까지 패턴은 시퀀스를 처리했지만, 존재하는 키를 기반으로 매핑을 매칭하는 패턴도 있습니다. 이 경우 다음과 같이 사용할 수 있습니다.
for action in actions:
match action:
case {"text": message, "color": c}:
ui.set_text_color(c)
ui.display(message)
case {"sleep": duration}:
ui.wait(duration)
case {"sound": url, "format": "ogg"}:
ui.play(url)
case {"sound": _, "format": _}:
warning("Unsupported audio format")
매핑 패턴의 키는 리터럴이어야 하지만, 값은 모든 패턴이 될 수 있습니다. 시퀀스 패턴과 마찬가지로, 일반 패턴이 매칭되려면 모든 서브 패턴이 매칭되어야 합니다.
매핑 패턴 내에서 **rest
를 사용하여 주제의 추가 키를 캡처할 수 있습니다. 이를 생략하면 매칭 시 주제의 추가 키는 무시됩니다. 즉, {"text": "foo", "color": "red", "style": "bold"}
메시지는 위 예시의 첫 번째 패턴과 매칭됩니다.
내장 클래스 매칭 (Matching builtin classes)
위의 코드는 일부 유효성 검사가 필요할 수 있습니다. 메시지가 외부 소스에서 왔으므로 필드의 유형이 잘못되어 버그나 보안 문제로 이어질 수 있습니다.
모든 클래스는 유효한 매칭 대상이며, 여기에는 bool
, str
, int
와 같은 내장 클래스도 포함됩니다. 이를 통해 위의 코드를 클래스 패턴과 결합할 수 있습니다. 따라서 {"text": message, "color": c}
를 작성하는 대신 {"text": str() as message, "color": str() as c}
를 사용하여 message
와 c
가 모두 문자열인지 확인할 수 있습니다. 많은 내장 클래스(전체 목록은 PEP 634 참조)의 경우 str() as c
대신 str(c)
와 같이 위치 매개변수를 축약형으로 사용할 수 있습니다. 완전히 다시 작성된 버전은 다음과 같습니다.
for action in actions:
match action:
case {"text": str(message), "color": str(c)}:
ui.set_text_color(c)
ui.display(message)
case {"sleep": float(duration)}:
ui.wait(duration)
case {"sound": str(url), "format": "ogg"}:
ui.play(url)
case {"sound": _, "format": _}:
warning("Unsupported audio format")
부록 A – 빠른 소개 (Appendix A – Quick Intro)
match
문은 표현식을 취하고 그 값을 하나 이상의 case
블록으로 주어진 연속적인 패턴과 비교합니다. 이는 겉으로는 C, Java 또는 JavaScript (및 기타 여러 언어)의 switch
문과 유사하지만 훨씬 더 강력합니다.
가장 간단한 형태는 주제 값을 하나 이상의 리터럴과 비교합니다.
def http_error(status):
match status:
case 400:
return "Bad request"
case 404:
return "Not found"
case 418:
return "I'm a teapot"
case _:
return "Something's wrong with the Internet"
마지막 블록에 주목하세요. “변수 이름” _
는 와일드카드(wildcard) 역할을 하며 절대 매칭에 실패하지 않습니다.
|
(“or”)를 사용하여 여러 리터럴을 단일 패턴으로 결합할 수 있습니다.
case 401 | 403 | 404:
return "Not allowed"
패턴은 언패킹 할당과 유사하게 보일 수 있으며, 변수를 바인딩하는 데 사용될 수 있습니다.
# point is an (x, y) tuple
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")
이것을 주의 깊게 살펴보세요! 첫 번째 패턴은 두 개의 리터럴을 가지며, 위에 표시된 리터럴 패턴의 확장으로 생각할 수 있습니다. 그러나 다음 두 패턴은 리터럴과 변수를 결합하며, 변수는 주제(point
)에서 값을 바인딩합니다. 네 번째 패턴은 두 값을 캡처하며, 이는 개념적으로 (x, y) = point
언패킹 할당과 유사합니다.
데이터를 구조화하기 위해 클래스를 사용하는 경우, 생성자와 유사한 인자 목록 뒤에 클래스 이름을 사용하여 속성을 변수에 캡처할 수 있습니다.
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
def where_is(point):
match point:
case Point(x=0, y=0):
print("Origin")
case Point(x=0, y=y):
print(f"Y={y}")
case Point(x=x, y=0):
print(f"X={x}")
case Point():
print("Somewhere else")
case _:
print("Not a point")
속성에 대한 순서를 제공하는 일부 내장 클래스(예: dataclass
)와 함께 위치 매개변수를 사용할 수 있습니다. 또한 클래스에서 __match_args__
특수 속성을 설정하여 패턴에서 속성에 대한 특정 위치를 정의할 수도 있습니다. ("x", "y")
로 설정된 경우 다음 패턴은 모두 동일합니다 (그리고 모두 y
속성을 var
변수에 바인딩합니다).
Point(1, var)
Point(1, y=var)
Point(x=1, y=var)
Point(y=var, x=1)
패턴은 임의로 중첩될 수 있습니다. 예를 들어, 짧은 point
리스트가 있다면 다음과 같이 매칭할 수 있습니다.
match points:
case []:
print("No points")
case [Point(0, 0)]:
print("The origin")
case [Point(x, y)]:
print(f"Single point {x}, {y}")
case [Point(0, y1), Point(0, y2)]:
print(f"Two on the Y axis at {y1}, {y2}")
case _:
print("Something else")
“가드(guard)”라고 알려진 if
절을 패턴에 추가할 수 있습니다. 가드가 거짓이면 match
는 다음 case
블록을 시도합니다. 값 캡처는 가드가 평가되기 전에 발생합니다.
match point:
case Point(x, y) if x == y:
print(f"Y=X at {x}")
case Point(x, y):
print(f"Not on the diagonal")
몇 가지 다른 주요 기능:
- 언패킹 할당과 마찬가지로, 튜플 및 리스트 패턴은 정확히 동일한 의미를 가지며 실제로는 임의의 시퀀스와 매칭됩니다. 중요한 예외는 이들이 이터레이터(iterators)나 문자열(strings)과 매칭되지 않는다는 것입니다. (기술적으로, 주제는
collections.abc.Sequence
의 인스턴스여야 합니다). - 시퀀스 패턴은 와일드카드를 지원합니다:
[x, y, *rest]
및(x, y, *rest)
는 언패킹 할당의 와일드카드와 유사하게 작동합니다.*
뒤의 이름은_
일 수도 있으므로,(x, y, *_)
는 나머지 항목을 바인딩하지 않고 최소 두 개의 항목으로 구성된 시퀀스와 매칭됩니다. - 매핑 패턴:
{"bandwidth": b, "latency": l}
는 딕셔너리에서"bandwidth"
및"latency"
값을 캡처합니다. 시퀀스 패턴과 달리, 추가 키는 무시됩니다. 와일드카드**rest
도 지원됩니다. (하지만**_
는 중복되므로 허용되지 않습니다.) - 서브 패턴은
as
키워드를 사용하여 캡처될 수 있습니다.case (Point(x1, y1), Point(x2, y2) as p2): ...
- 대부분의 리터럴은 동등성(equality)으로 비교되지만, 싱글톤(singletons)인
True
,False
,None
은 동일성(identity)으로 비교됩니다. - 패턴은 이름 지정된 상수(named constants)를 사용할 수 있습니다. 이들은 캡처 변수로 해석되는 것을 방지하기 위해 점(dotted) 이름이어야 합니다.
from enum import Enum class Color(Enum): RED = 0 GREEN = 1 BLUE = 2 match color: case Color.RED: print("I see red!") case Color.GREEN: print("Grass is green") case Color.BLUE: print("I'm feeling the blues :(")
저작권 (Copyright)
이 문서는 퍼블릭 도메인(public domain)에 공개되거나 CC0-1.0-Universal 라이선스 중 더 관대한 조건에 따릅니다.
출처 (Source): https://github.com/python/peps/blob/main/peps/pep-0636.rst
최종 수정 (Last modified): 2025-02-01 08:59:27 GMT
⚠️ 알림: 이 문서는 AI를 활용하여 번역되었으며, 기술적 정확성을 보장하지 않습니다. 정확한 내용은 반드시 원문을 확인하시기 바랍니다.
Comments