PEP 636 – 구조적 패턴 매칭: 튜토리얼
- Author:
- Daniel F Moisset <dfmoisset at gmail.com>
- Sponsor:
- Guido van Rossum <guido at python.org>
- BDFL-Delegate:
- Discussions-To:
- Python-Dev list
- Status:
- Final
- Type:
- Informational
- Created:
- 12-Sep-2020
- Python-Version:
- 3.10
- Post-History:
- 22-Oct-2020, 08-Feb-2021
- Resolution:
- Python-Committers message
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
이 PEP는 PEP 634에서 도입된 패턴 매칭에 대한 튜토리얼입니다.
PEP 622는 패턴 매칭을 위한 문법을 제안했으며, 이 제안은 커뮤니티와 운영 위원회(Steering Council) 양쪽에서 상세하게 논의되었습니다. 자주 제기된 우려는 이 기능을 설명하기가(그리고 배우기가) 얼마나 쉬울 것인가에 관한 것이었습니다. 이 PEP는 개발자가 Python의 패턴 매칭에 대해 배우는 데 사용할 수 있는 문서를 제공하여 그 우려에 대응합니다.
이 문서는 PEP 634 (패턴 매칭의 기술 명세)와 PEP 635 (패턴 매칭 도입의 동기와 근거, 그리고 설계 고려 사항)의 보조 자료로 간주됩니다.
튜토리얼보다는 빠르게 훑어보기를 원하는 독자는 부록 A를 참조하십시오.
튜토리얼
이 튜토리얼의 동기를 부여하는 예제로, 여러분은 텍스트 어드벤처를 작성하게 됩니다. 텍스트 어드벤처는 사용자가 텍스트 명령을 입력하여 가상 세계와 상호 작용하고, 일어나는 일에 대한 텍스트 설명을 받는 인터랙티브 픽션의 한 형태입니다. 명령은 get sword, attack dragon, go north, enter shop, buy cheese와 같이 자연어를 단순화한 형태가 됩니다.
시퀀스 매칭하기
메인 루프는 사용자로부터 입력을 받아 단어들로 나누어야 합니다. 예를 들어 다음과 같은 문자열 리스트가 된다고 합시다:
command = input("What are you doing next? ")
# analyze the result of command.split()
다음 단계는 단어들을 해석하는 것입니다. 대부분의 명령은 두 단어, 즉 동작과 객체로 구성됩니다. 그래서 다음과 같이 하고 싶어질 수 있습니다:
[action, obj] = command.split()
... # interpret action, obj
이 코드 줄의 문제는 무언가를 놓치고 있다는 점입니다. 사용자가 2개보다 많거나 적은 단어를 입력하면 어떻게 됩니까? 이 문제를 방지하려면 단어 리스트의 길이를 확인하거나, 위 문장이 발생시킬 ValueError를 잡아낼 수 있습니다.
대신 매치 문을 사용할 수 있습니다:
match command.split():
case [action, obj]:
... # interpret action, obj
매치 문은 “주체(subject)” (match 키워드 뒤의 값)를 평가하고, 이를 패턴 (case 옆의 코드)과 대조하여 검사합니다. 패턴은 두 가지 서로 다른 일을 할 수 있습니다:
- 주체가 특정한 구조를 가지고 있는지 확인합니다. 이 경우,
[action, obj]패턴은 정확히 두 개의 요소로 이루어진 모든 시퀀스와 매칭됩니다. 이것을 매칭이라고 합니다 - 패턴 안의 일부 이름을 주체의 구성 요소에 바인딩합니다. 이 경우, 리스트에 두 개의 요소가 있으면
action = subject[0]과obj = subject[1]을 바인딩합니다.
매칭이 성공하면, 바인딩된 변수를 가지고 case 블록 안의 문장들이 실행됩니다. 매칭이 실패하면 아무 일도 일어나지 않고 match 다음의 문장이 이어서 실행됩니다.
언패킹 대입과 비슷하게, 소괄호나 대괄호를 쓰거나 쉼표로만 구분하는 방식을 같은 뜻으로 사용할 수 있다는 점에 유의하십시오. 따라서 case action, obj로 쓰든 case (action, obj)로 쓰든 같은 의미가 됩니다. 모든 형태는 임의의 시퀀스(예를 들어 리스트나 튜플)와 매칭됩니다.
여러 패턴 매칭하기
대부분의 명령이 동작/객체 형태를 갖더라도, 길이가 다른 사용자 명령을 두고 싶을 수 있습니다. 예를 들어 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/... 문이 동작하는 방식과 비슷합니다.
특정 값 매칭하기
코드는 여전히 구체적인 동작을 살펴보고, 그 동작(예: 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 연산자로 비교됩니다.
여러 값 매칭하기
플레이어는 drop key, drop sword, drop cheese 같은 일련의 명령을 사용해 여러 아이템을 버릴 수 있습니다. 이런 인터페이스는 번거로울 수 있으므로, drop key sword cheese처럼 하나의 명령으로 여러 아이템을 버릴 수 있게 하고 싶을 수 있습니다. 이 경우 명령에 단어가 몇 개 들어올지 미리 알 수 없지만, 대입문에서 허용되는 것과 같은 방식으로 패턴에서도 확장 언패킹을 사용할 수 있습니다:
match command.split():
case ["drop", *objects]:
for obj in objects:
character.drop(obj, current_room)
# The rest of your commands go here
이는 첫 번째 요소가 “drop”인 모든 시퀀스에 일치합니다. 나머지 요소는 모두 list 객체에 담기고, 이 객체는 objects 변수에 바인딩됩니다.
이 문법에는 시퀀스 언패킹과 비슷한 제약이 있습니다. 한 패턴에 별표가 붙은 이름을 둘 이상 사용할 수 없습니다.
와일드카드 추가하기
모든 패턴이 실패했을 때 명령을 인식하지 못했다는 오류 메시지를 출력하고 싶을 수 있습니다. 방금 배운 기능을 사용해 마지막 패턴으로 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}")
_로 작성하는 이 특수 패턴(와일드카드라고 부릅니다)은 항상 일치하지만 어떤 변수도 바인딩하지 않습니다.
이 패턴은 시퀀스뿐만 아니라 모든 객체에 일치한다는 점에 유의하십시오. 따라서 이 패턴은 마지막 패턴으로 단독으로 두는 경우에만 의미가 있습니다(오류를 방지하기 위해 Python은 이 패턴을 그 앞에 사용하지 못하게 합니다).
패턴 조합하기
이제 예제에서 잠시 물러나, 지금까지 사용해 온 패턴이 어떻게 구성되는지 이해하기에 좋은 시점입니다. 패턴은 서로 중첩될 수 있으며, 위의 예제에서 우리는 암묵적으로 그렇게 해 왔습니다.
지금까지 살펴본 “단순한” 패턴(“단순하다”는 것은 여기서 다른 패턴을 포함하지 않는다는 뜻입니다)이 몇 가지 있습니다.
- 캡처 패턴 (
direction,action,objects같은 단독 이름). 이 패턴을 따로 다룬 적은 없지만, 다른 패턴의 일부로 사용해 왔습니다. - 리터럴 패턴 (문자열 리터럴, 숫자 리터럴,
True,False,None) - 와일드카드 패턴
_
지금까지 실험해 본 단순하지 않은 패턴은 시퀀스 패턴뿐입니다. 사실 시퀀스 패턴의 각 요소는 다른 어떤 패턴이든 될 수 있습니다. 즉, ["first", (left, right), _, *rest] 같은 패턴을 작성할 수 있습니다. 이 패턴은 요소가 최소 세 개인 시퀀스이면서, 첫 번째 요소가 "first"와 같고 두 번째 요소가 다시 두 요소로 된 시퀀스인 대상과 매치됩니다. 또한 다음과 같이 바인딩합니다: left=subject[1][0], right=subject[1][1], rest = subject[3:]
or 패턴
어드벤처 게임 예제로 돌아가 보면, 여러 패턴이 같은 결과를 내도록 하고 싶을 때가 있을 것입니다. 예를 들어, north와 go north 명령이 같은 의미가 되기를 원할 수 있습니다. 또한 임의의 X에 대해 get X, pick up X, pick X up을 별칭으로 두고 싶을 수도 있습니다.
패턴 안의 | 기호는 여러 패턴을 대안으로 묶어 줍니다. 예를 들어 다음과 같이 작성할 수 있습니다.:
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 패턴이라고 하며, 기대한 결과를 만들어 냅니다. 패턴은 왼쪽에서 오른쪽으로 시도됩니다. 둘 이상의 대안이 매치될 때 무엇이 바인딩되는지 알려면 이 점이 중요할 수 있습니다. or 패턴을 작성할 때의 중요한 제약은 모든 대안이 같은 변수를 바인딩해야 한다는 것입니다. 따라서 [1, x] | [2, y] 패턴은 매치에 성공한 뒤 어떤 변수가 바인딩될지 불분명해지므로 허용되지 않습니다. [1, x] | [2, x]는 전혀 문제가 없으며, 성공하면 항상 x를 바인딩합니다.
매치된 하위 패턴 캡처하기
“go” 명령의 첫 번째 버전은 ["go", direction] 패턴으로 작성했습니다. 마지막 버전에서 ["north"] | ["go", "north"] 패턴을 사용하도록 바꾼 것은 이전과 비교해 장점도 있지만 단점도 있습니다. 최신 버전은 별칭을 허용하지만 방향이 하드코딩되어 있어서, 실제로 north/south/east/west마다 별도의 패턴을 두어야 합니다. 이로 인해 코드가 어느 정도 중복되지만, 동시에 입력 검증이 더 나아지며, 사용자가 입력한 명령이 방향이 아니라 "go figure!"인 경우에는 그 브랜치로 들어가지 않게 됩니다.
다음과 같이 하여 두 방식의 장점을 모두 취해 볼 수 있습니다(간결함을 위해 “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 패턴처럼 동작하면서 동시에 캡처도 수행하는 패턴입니다. 이는 as 패턴으로 할 수 있습니다.:
match command.split():
case ["go", ("north" | "south" | "east" | "west") as direction]:
current_room = current_room.neighbor(direction)
as 패턴은 왼쪽에 있는 패턴이 무엇이든 그 패턴과 매치하고, 추가로 그 값을 이름에 바인딩합니다.
패턴에 조건 추가하기
위에서 살펴본 패턴으로도 강력한 데이터 필터링을 할 수 있지만, 때로는 불리언 표현식의 모든 기능이 필요할 수도 있습니다. 실제로 current_room에서 나갈 수 있는 출구에 따라 제한된 방향에 대해서만 “go” 명령을 허용하고 싶다고 가정해 봅시다. case에 가드를 추가하면 이를 구현할 수 있습니다. 가드는 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")
가드는 패턴의 일부가 아니라 case의 일부입니다. 가드는 패턴이 매치될 때만, 그리고 모든 패턴 변수가 바인딩된 후에만 검사됩니다(위 예제에서 조건이 direction 변수를 사용할 수 있는 것도 이 때문입니다). 패턴이 매치되고 조건이 참으로 평가되면 case의 본문이 정상적으로 실행됩니다. 패턴은 매치되었지만 조건이 거짓으로 평가되면, match 문은 패턴이 매치되지 않은 것처럼 다음 case를 검사합니다(이때 일부 변수가 이미 바인딩되어 있을 수 있다는 부작용이 있습니다).
UI 추가하기: 객체 매칭
여러분의 어드벤처 게임이 성공을 거두고 있어서 그래픽 인터페이스를 구현해 달라는 요청을 받았습니다. 여러분이 선택한 UI 툴킷으로는 event.get()을 호출하여 새 이벤트 객체를 얻는 이벤트 루프를 작성할 수 있습니다. 반환되는 객체는 사용자 동작에 따라 타입과 속성이 다를 수 있습니다. 예를 들면 다음과 같습니다:
- 사용자가 키를 누르면
KeyPress객체가 생성됩니다. 이 객체에는 누른 키의 이름을 담은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 클래스의 인스턴스인 모든 객체와 매치됩니다. 패턴에 지정한 속성만 매치되며, 그 밖의 속성은 무시됩니다.
위치 속성 매칭
앞 절에서는 객체 매치를 할 때 이름이 있는 속성을 매치하는 방법을 설명했습니다. 어떤 객체는 매치할 인자를 위치로 기술하는 편이 편리할 수 있습니다(특히 속성이 몇 개뿐이고 “표준” 순서가 있는 경우에 그렇습니다). 사용하는 클래스가 네임드 튜플이나 데이터클래스라면, 객체를 생성할 때와 같은 순서를 따르는 방식으로 그렇게 할 수 있습니다. 예를 들어 위의 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 속성과 매치됩니다.
다른 클래스에는 속성의 자연스러운 순서가 없으므로, 속성과 매칭하려면 패턴에서 명시적인 이름을 사용해야 합니다. 하지만 다음의 대체 정의처럼 속성의 순서를 직접 지정하여 위치 매칭을 허용할 수 있습니다.:
class Click:
__match_args__ = ("position", "button")
def __init__(self, pos, btn):
self.position = pos
self.button = btn
...
__match_args__ 특수 속성은 case Click((x,y))와 같은 패턴에서 사용할 수 있는 속성의 명시적 순서를 정의합니다.
상수 및 열거형과의 매칭
위의 패턴은 모든 마우스 버튼을 똑같이 처리하는데, 여러분은 왼쪽 클릭은 받아들이고 다른 버튼은 무시하기로 했습니다. 그렇게 하는 과정에서 button 속성의 타입이 enum.Enum으로 만든 열거형인 Button으로 지정되어 있다는 것을 알게 됩니다. 실제로 다음과 같이 열거형 값과 매칭할 수 있습니다.:
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
이는 (math.pi 같은) 점으로 구분된 이름이라면 무엇이든 동작합니다. 하지만 한정되지 않은 이름(즉, 점이 없는 단순한 이름)은 항상 캡처 패턴으로 해석되므로, 패턴에서는 항상 한정된 상수를 사용하여 이러한 모호함을 피하십시오.
클라우드로: 매핑
여러분은 게임의 온라인 버전을 만들기로 했습니다. 모든 로직은 서버에 두고, 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"}는 위 예제의 첫 번째 패턴과 매칭됩니다.
내장 클래스와의 매칭
위의 코드에는 약간의 유효성 검사가 필요합니다. 메시지가 외부 출처에서 왔으므로 필드의 타입이 잘못되었을 수 있으며, 이는 버그나 보안 문제로 이어질 수 있습니다.
모든 클래스는 유효한 매칭 대상이며, 여기에는 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 – 빠른 소개
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"
마지막 블록에 주목하십시오. “변수 이름” _는 와일드카드 역할을 하며 매칭에 절대 실패하지 않습니다.
|(“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")
속성의 순서를 제공하는 일부 내장 클래스(예: 데이터클래스)에서는 위치 매개변수를 사용할 수 있습니다. 또한 클래스에 __match_args__ 특수 속성을 설정하여 패턴에서 속성의 특정 위치를 정의할 수도 있습니다. 이 속성이 (“x”, “y”)로 설정되어 있으면, 다음 패턴들은 모두 동등합니다(그리고 모두 y 속성을 var 변수에 바인딩합니다).:
Point(1, var)
Point(1, y=var)
Point(x=1, y=var)
Point(y=var, x=1)
패턴은 임의로 중첩될 수 있습니다. 예를 들어, 점들의 짧은 리스트가 있다면 다음과 같이 매칭할 수 있습니다:
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")
패턴에 “가드”라고 하는 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")
그 밖의 몇 가지 주요 기능은 다음과 같습니다:
- 언패킹 대입과 마찬가지로, 튜플 패턴과 리스트 패턴은 완전히 같은 의미를 가지며 실제로는 임의의 시퀀스와 매칭됩니다. 중요한 예외는 이터레이터나 문자열과는 매칭되지 않는다는 점입니다. (엄밀히 말하면, 대상은
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): ...
- 대부분의 리터럴은 동등성으로 비교되지만, 싱글턴인
True,False와None은 동일성으로 비교됩니다. - 패턴에는 이름 있는 상수를 사용할 수 있습니다. 이러한 상수는 캡처 변수로 해석되지 않도록 점으로 구분된 이름이어야 합니다:
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
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.