PEP 642 – 구조적 패턴 매칭을 위한 명시적 패턴 문법
- Author:
- Alyssa Coghlan <ncoghlan at gmail.com>
- BDFL-Delegate:
- Discussions-To:
- Python-Dev list
- Status:
- Rejected
- Type:
- Standards Track
- Requires:
- 634
- Created:
- 26-Sep-2020
- Python-Version:
- 3.10
- Post-History:
- 31-Oct-2020, 08-Nov-2020, 03-Jan-2021
- Resolution:
- Python-Dev message
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
이 PEP는 PEP 634의 구조적 패턴 매칭에 대한 대안 문법 제안을 다루며, 이 제안은 모든 캡처 패턴과 값 제약 조건에 명시적인 접두사를 요구합니다. 또한 제안된 매핑 패턴 문법과 더 긴밀하게 맞춰지는, 인스턴스 속성 패턴을 위한 새로운 전용 문법을 제안합니다.
그 결과는 PEP 634에서 제안된 문법보다 필연적으로 더 장황하지만, 여전히 현재 상태보다는 훨씬 덜 장황합니다.
예를 들어, 다음 match 문은 2개 항목으로 된 시퀀스, “host”와 “port” 키를 가진 매핑, “host”와 “port” 속성을 가진 임의의 객체, 또는 “host:port” 문자열에서 “host”와 “port” 정보를 추출하며, 뒤의 세 경우에는 “port”를 선택 사항으로 취급합니다:
port = DEFAULT_PORT
match expr:
case [as host, as port]:
pass
case {"host" as host, "port" as port}:
pass
case {"host" as host}:
pass
case object{.host as host, .port as port}:
pass
case object{.host as host}:
pass
case str{} as addr:
host, __, optional_port = addr.partition(":")
if optional_port:
port = optional_port
case __ as m:
raise TypeError(f"Unknown address format: {m!r:.200}")
port = int(port)
큰 틀에서, 이 PEP는 사용 가능한 여러 패턴 유형을 다음과 같이 분류할 것을 제안합니다:
- 와일드카드 패턴:
__ - 그룹 패턴:
(PTRN) - 값 제약 패턴:
- 동등성 제약:
== EXPR - 동일성 제약:
is EXPR
- 동등성 제약:
- 구조적 제약 패턴:
- 시퀀스 제약 패턴:
[PTRN, as NAME, PTRN as NAME] - 매핑 제약 패턴:
{EXPR: PTRN, EXPR as NAME} - 인스턴스 속성 제약 패턴:
CLS{.NAME, .NAME: PTRN, .NAME == EXPR, .NAME as NAME} - 클래스 정의 제약 패턴:
CLS(PTRN, PTRN, **{.NAME, .NAME: PTRN, .NAME == EXPR, .NAME as NAME})
- 시퀀스 제약 패턴:
- OR 패턴:
PTRN | PTRN | PTRN - AS 패턴:
PTRN as NAME(패턴을 생략하면__를 의미합니다)
이 접근 방식의 의도는 다음과 같습니다:
- 맨 이름, 속성 조회, 리터럴 값을 처리하는 최선의 기본 방식을 미리 결정하지 않고도 패턴 매칭의 초기 형태를 개발하고 출시할 수 있게 합니다
- 패턴 매칭이 추상 구문 트리(Abstract Syntax Tree) 수준에서 명시적으로 정의되도록 보장하여, 패턴 매칭의 의미 명세와 표면 문법 명세를 명확하게 분리할 수 있게 합니다
- 같은 객체에서 여러 속성을 담은 튜플을 더 쉽게 가져오는 방법으로 일반 표현식에도 채택될 가능성이 있는, 명확하고 간결한 “덕 타이핑” 문법을 정의합니다
PEP 634와 비교하여, 이 제안은 as 키워드를 사용하지 않고 “오른쪽으로 바인딩하는” 문법(PEP 634의 매핑 패턴과 클래스 패턴에서 캡처 패턴을 사용하는 경우)이나 같은 패턴에서 왼쪽과 오른쪽 모두에 바인딩하는 문법(PEP 634의 캡처 패턴을 AS 패턴과 함께 사용하는 경우)을 의도적으로 모두 제거합니다
다른 PEP와의 관계
이 PEP는 PEP 634에 의존하는 동시에 그것과 경쟁합니다. PEP 작성자는 match 문이 학습 과정에 더하는 추가적인 복잡성을 감수할 만큼 언어에 충분히 가치 있는 추가 기능이라는 데 동의하지만, “단순 이름 대 리터럴 또는 속성 조회”가 match 패턴에서 이름 바인딩과 값 조회 연산을 구문적으로 적절하게 구분해 준다는 생각에는 (적어도 Python에서는) 동의하지 않습니다.
이 PEP는 PEP 640의 취지(이름 바인딩을 건너뛰기 위해 선택된 와일드카드 패턴은 match 패턴에서만이 아니라 모든 곳에서 지원되어야 한다는 것)에 동의하지만, 이제는 와일드카드 문법에 대해 다른 표기(? 대신 __)를 제안하고 있습니다. 따라서 이 PEP는 현재 작성된 형태의 PEP 640과는 경쟁하지만, __를 일반 식별자로 사용하는 것을 폐지하고 대신 항상 새로운 지역 변수 바인딩 생성을 건너뛰는 범용 와일드카드 표식으로 바꾸자는 제안과는 상호 보완적일 것입니다.
아직 PEP로 제출되지는 않았지만, Mark Shannon은 PEP 634의 패턴 매칭 제안이 가진 런타임 의미론에 대한 여러 우려를 표명하는 사전 PEP 초안 [8]을 작성했습니다. 이 PEP는 그 초안과 어느 정도 상호 보완적입니다. 이 PEP는 주요한 의미 변경보다는 주로 표면 문법 변경에 관한 것이지만, 표면 문법의 세부 사항을 코드 생성 단계의 의미와 더 잘 분리하기 위해 추상 구문 트리 정의를 더 명시적으로 만들 것을 제안하기 때문입니다. 그 사전 PEP 초안에는 이 PEP가 명시적으로 거부하는 구체적인 아이디어가 하나 있습니다: 서로 다른 종류의 매칭이 상호 배타적이라는 아이디어입니다. 같은 값이 서로 다른 종류의 구조적 패턴과 매칭되는 것은 충분히 가능하며, 어느 쪽이 우선하는지는 의도적으로 match 문에 있는 case의 순서에 따라 결정됩니다.
동기
원래의 PEP 622(나중에 PEP 634, PEP 635, PEP 636으로 분리됨)는 문법 설계에 명시되지는 않았지만 핵심적인 가정을 담고 있었습니다. 그것은 일반 표현식과 기존 대입 대상 문법 어느 쪽도 match 패턴에 쓰이는 문법의 적절한 토대를 제공하지 못한다는 가정입니다.
해당 PEP는 이 가정을 명시적으로 밝히지 않았지만, PEP 작성자 중 한 명이 python-dev에서 이를 명확하게 설명했습니다 [1]:
제가 보기에 실제 문제는 서로 다른 문화와 직관이 여기서 근본적으로 충돌하고 있다는 점입니다. 특히 많은 프로그래머가 패턴 매칭을 “확장된 switch 문”으로 반기며, 그렇기 때문에 이름이 비교를 위한 표현식이 아니라 바인딩이라는 점을 이상하게 여깁니다. 반면 다른 사람들은 이것이 이를테면 현재의 대입문과 맞지 않는다고 주장하며, 왜 점으로 구분된 이름은 바인딩이 _/되지 않는지/_ 의문을 제기합니다. 하지만 모든 그룹에 공통적인 것은, 새로운 match 문에 대한 _/자신의/_ 이해와 해석을 ‘일관적’이라거나 ‘직관적’이라고 일컫는다는 점입니다 — 당연히 PEP 작성자인 우리가 설계에서 어디를 잘못했는지 지적하면서 말입니다.하지만 여기에 함정이 있습니다. 적어도 Python 세계에서는, 이 PEP가 제안하는 패턴 매칭은 흔한 문제에 접근하는 전례 없는 새로운 방식입니다. 이미 있는 무언가를 단순히 확장한 것이 아닙니다. 더 나쁜 점은, PEP를 설계하면서 우리는 어떤 각도에서 접근하든 겉보기에 ‘비일관성’으로 보이는 문제에 부딪힌다는 것을 발견했다는 것입니다(즉, 패턴 매칭은 기존 기능의 ‘선형적’ 확장으로 의미 있게 환원될 수 없습니다). Python에 이미 있는 것을 근본적으로 넘어서는 무언가가 항상 존재합니다. 그래서 저는 무엇이 ‘직관적’인지 또는 ‘일관적’인지에 근거한 인자가 _/이 경우에는/_ 전혀 말이 되지 않는다고 주장합니다.
이 PEP의 첫 번째 버전은 두 번째 주장이 정확하지 않으며, 매치 패턴을 내재적 모순 없이 대입 대상의 변형으로 다룰 수 있음을 보이려는 시도에서 탄생했습니다. (이 선택지를 원래 PEP 622의 “Rejected Ideas” 섹션에 추가하자고 제출한 이전 PR은 앞서 거절된 바 있습니다 [2]).
그러나 이 PEP의 검토 과정은 Tobias가 이메일에서 언급한 모순이 실제로 존재할 뿐만 아니라, 그 모순이 PEP 634에서 제시된 문법 제안에 의문을 제기할 만큼 우려스럽다는 점을 강하게 시사했습니다. 이에 따라 이 PEP는 PEP 634보다 한층 더 나아가, 시퀀스 매칭 문법과 기존 이터러블 언패킹 문법 사이의 정렬을 대부분 포기하도록 변경되었습니다(사실상 DLS’20 논문 [9] 에서 제기된 첫 번째 질문인 “이터러블 언패킹 같은 기능을 더 일반적인 객체 및 데이터 레이아웃에서도 작동하도록 확장할 수 있는가?”에 “정확한 문법에 관한 한, 사실 그렇지 않다”라고 답하는 셈입니다).
이로써 PEP의 목표가 완전히 뒤바뀌었습니다. 이제 PEP는 대입과 패턴 매칭 사이의 유사성을 강조하려 하기보다는, 대입 대상 문법이 전혀 재사용되지 않도록 하여 기존 구문에 대한 경험을 바탕으로 새로운 구문에 대해 잘못된 추론이 이루어질 가능성을 줄이려 합니다.
마지막으로, 제안의 세 번째 반복(추론된 패턴을 완전히 없앤 버전)을 완성하기 전에, PEP 작성자는 PEP 20의 다음 항목들에 대해 상당한 시간을 들여 숙고했습니다:
- 명시적인 것이 암시적인 것보다 낫습니다.
- 특별한 경우라도 규칙을 깰 만큼 특별하지는 않습니다.
- 모호함 앞에서는 추측하려는 유혹을 거부하십시오.
명시적인 문법으로 시작하면 나중에 언제든지 구문적 단축 표기를 추가할 수 있지만(예를 들어, 원래의 더 장황한 형태를 수년간 사용해 본 뒤에야 Union과 Optional 타입 힌트에 대한 단축 표기를 추가하자는 최근의 제안들을 생각해 보십시오), 축약된 형태만으로 시작하면 향후 릴리스에서 그 결정들을 재검토할 실질적인 방법이 없습니다.
명세
이 PEP는 PEP 634의 전반적인 match/case 문 구조와 의미를 유지하지만, 사용자의 의도를 패턴 매칭 문맥에서 추론할 필요 없이 구체 문법에서 명시적으로 지정하도록 하는 여러 변경 사항을 제안합니다.
제안된 추상 문법 트리(Abstract Syntax Tree)에서는 의미 또한 항상 명시적이며, 추론이 전혀 필요하지 않습니다.
Match 문
표면 문법:
match_stmt: "match" subject_expr ':' NEWLINE INDENT case_block+ DEDENT
subject_expr:
| star_named_expression ',' star_named_expressions?
| named_expression
case_block: "case" (guarded_pattern | open_pattern) ':' block
guarded_pattern: closed_pattern 'if' named_expression
open_pattern:
| as_pattern
| or_pattern
closed_pattern:
| wildcard_pattern
| group_pattern
| structural_constraint
추상 문법:
Match(expr subject, match_case* cases)
match_case = (pattern pattern, expr? guard, stmt* body)
star_named_expression, star_named_expressions, named_expression, block 규칙은 표준 파이썬 문법의 일부입니다.
열린 패턴은 여러 토큰으로 이루어지며 반드시 닫는 구분자로 끝나지는 않는 패턴입니다(예: __ as x, int() | bool()). 사람이 읽을 때의 모호함을 피하기 위해, 열린 패턴은 최상위 패턴과 그룹 패턴(괄호로 둘러싸인 패턴)에서만 사용할 수 있습니다.
닫힌 패턴은 단일 토큰(즉, __)으로 구성되거나, 문법의 필수 부분으로 닫는 구분자를 갖는 패턴입니다(예: [as x, as y], object{.x as x, .y as y}).
PEP 634에서와 마찬가지로 match와 case 키워드는 소프트 키워드입니다. 즉, 다른 문법적 문맥(예상되는 위치에 콜론이 없는 경우의 줄 시작 부분 포함)에서는 예약어가 아닙니다. 이는 이 키워드들이 match 문이나 case 블록의 일부일 때에만 키워드로 인식되며, 그 밖의 모든 문맥에서는 변수나 인자 이름으로 사용할 수 있다는 뜻입니다.
PEP 634와 달리, 패턴은 추상 구문 트리에서 새로운 종류의 노드로 명시적으로 정의됩니다. 표면 문법이 기존 표현식 노드와 공유되는 경우에도, 파서는 별개의 추상 노드를 생성합니다.
참고로, match_stmt는 표면 문법에서 compound_statement의 새로운 대안이며, Match는 추상 문법에서 stmt의 새로운 대안입니다.
매치 의미론
이 PEP는 PEP 634에서 제안된 전반적인 패턴 매칭 의미론을 대체로 유지합니다.
패턴에 대해 제안된 문법은 크게 바뀌며, 아래에서 자세히 논의합니다.
또한 어떤 내장 타입도 특별 취급할 필요가 없도록 클래스 정의 제약 조건(PEP 634의 클래스 패턴)의 의미에 대한 몇 가지 변경도 제안합니다(대신, 인스턴스 속성 제약 조건을 위한 전용 문법을 도입함으로써 그러한 내장 타입에 필요한 동작을 __match_args__를 None으로 설정하는 모든 타입에 적용되는 것으로 지정할 수 있습니다)
가드
이 PEP는 PEP 634에서 제안된 가드 절 의미론을 유지합니다.
그러나 가드 절이 있을 때는 case 패턴이 반드시 닫힌 패턴이어야 하도록 문법이 약간 변경됩니다.
이렇게 하면 패턴이 어디에서 끝나고 가드 절이 어디에서 시작하는지 독자가 더 명확하게 알 수 있습니다. (이는 주로 OR 패턴에서 생길 수 있는 문제인데, 가드 절이 마지막 패턴 안에 있는 조건 표현식의 시작 부분처럼 보이기 때문입니다. 실제로 그렇게 하는 것은 올바른 문법이 아니므로 컴파일러 입장에서는 모호함이 없지만, 사람인 독자에게는 그 구분이 그만큼 명확하지 않을 수 있습니다)
반박 불가능한 case 블록
캡처 패턴이 더 이상 AS 패턴과 별개의 개념으로 존재하지 않으므로, 이 PEP에서는 반박 불가능한 case 블록의 정의가 PEP 634에 비해 약간 변경됩니다.
그 단서를 제외하면, 반박 불가능한 case의 처리는 PEP 634에서와 같습니다:
- 와일드카드 패턴은 반박 불가능합니다
- 왼쪽 항이 반박 불가능한 AS 패턴
- 반박 불가능한 패턴을 하나 이상 포함하는 OR 패턴
- 괄호로 묶인 반박 불가능한 패턴
- case 블록에 가드가 없고 그 패턴이 반박 불가능하면 해당 case 블록은 반박 불가능한 것으로 간주됩니다.
- match 문에는 반박 불가능한 case 블록이 최대 하나만 있을 수 있으며, 그 블록은 반드시 마지막에 있어야 합니다.
패턴
패턴의 최상위 표면 문법은 다음과 같습니다:
open_pattern: # Pattern may use multiple tokens with no closing delimiter
| as_pattern
| or_pattern
as_pattern: [closed_pattern] pattern_as_clause
or_pattern: '|'.simple_pattern+
simple_pattern: # Subnode where "as" and "or" patterns must be parenthesised
| closed_pattern
| value_constraint
closed_pattern: # Require a single token or a closing delimiter in pattern
| wildcard_pattern
| group_pattern
| structural_constraint
위에서 설명한 것처럼, 열린 패턴은 최상위 case 절에서, 그리고 그룹 패턴 안에서 괄호로 묶였을 때만 사용할 수 있습니다.
패턴의 추상 문법은 어떤 요소가 하위 패턴이고 어떤 요소가 하위 표현식이나 식별자인지를 명시적으로 나타냅니다:
pattern = MatchAlways
| MatchValue(matchop op, expr value)
| MatchSequence(pattern* patterns)
| MatchMapping(expr* keys, pattern* patterns)
| MatchAttrs(expr cls, identifier* attrs, pattern* patterns)
| MatchClass(expr cls, pattern* patterns, identifier* extra_attrs, pattern* extra_patterns)
| MatchRestOfSequence(identifier? target)
-- A NULL entry in the MatchMapping key list handles capturing extra mapping keys
| MatchAs(pattern? pattern, identifier target)
| MatchOr(pattern* patterns)
AS 패턴
표면 문법:
as_pattern: [closed_pattern] pattern_as_clause
pattern_as_clause: 'as' pattern_capture_target
pattern_capture_target: !"__" NAME !('.' | '(' | '=')
(참고: 오른쪽의 이름은 __일 수 없습니다.)
추상 문법:
MatchAs(pattern? pattern, identifier target)
AS 패턴은 as 키워드 왼쪽의 닫힌 패턴을 대상과 매치합니다. 이 매치가 실패하면 AS 패턴도 실패합니다. 그렇지 않으면 AS 패턴은 대상을 as 키워드 오른쪽의 이름에 바인딩하고 성공합니다.
매치할 패턴이 주어지지 않으면 와일드카드 패턴(__)이 암시됩니다.
Wildcard Pattern과 혼동하지 않도록 이중 밑줄(__)은 캡처 대상으로 허용되지 않습니다(!"__"가 표현하는 것이 바로 이것입니다).
캡처 패턴은 항상 성공합니다. 캡처 패턴은 PEP 572에서 이름 있는 표현식에 대해 정한 이름 바인딩 스코프 규칙에 따라 대상 값을 이름에 바인딩합니다. (요약: 적용되는 nonlocal 또는 global 문이 없으면, 이름은 가장 가까운 바깥 함수 스코프의 지역 변수가 됩니다.)
하나의 패턴 안에서 같은 이름은 한 번만 바인딩될 수 있습니다. 예를 들어 case [as x, as x]: ...는 허용되지 않지만 case [as x] | (as x)는 허용됩니다:
열린 패턴이므로 AS 패턴은 최상위 case 절에서, 그리고 그룹 패턴 안에서 괄호로 묶인 경우에만 사용할 수 있습니다. 하지만 여러 구조적 제약에서는 매칭된 대상에서 추출한 요소를 지역 변수에 바인딩하기 위해 관련 위치에서 pattern_as_clause를 사용할 수 있습니다. 이들은 대부분 추상 문법 트리에서 MatchAs 노드로 표현되며, 시퀀스 패턴의 전용 MatchRestOfSequence 노드만 예외입니다.
OR 패턴
표면 문법:
or_pattern: '|'.simple_pattern+
simple_pattern: # Subnode where "as" and "or" patterns must be parenthesised
| closed_pattern
| value_constraint
추상 문법:
MatchOr(pattern* patterns)
둘 이상의 패턴이 세로 막대(|)로 구분되어 있으면 이를 OR 패턴이라고 합니다. (단순 패턴 하나는 그냥 단순 패턴입니다)
마지막 하위 패턴만 반박 불가능할 수 있습니다.
각 하위 패턴은 같은 이름 집합을 바인딩해야 합니다.
OR 패턴은 하위 패턴 중 하나가 성공할 때까지 각 하위 패턴을 차례로 대상과 매칭합니다. 그러면 OR 패턴은 성공한 것으로 간주됩니다. 하위 패턴이 하나도 성공하지 않으면 OR 패턴은 실패합니다.
하위 패턴은 대부분 닫힌 패턴이어야 하지만, 값 제약의 경우에는 괄호를 생략할 수 있습니다.
값 제약
표면 문법:
value_constraint:
| eq_constraint
| id_constraint
eq_constraint: '==' closed_expr
id_constraint: 'is' closed_expr
closed_expr: # Require a single token or a closing delimiter in expression
| primary
| closed_factor
closed_factor: # "factor" is the main grammar node for these unary ops
| '+' primary
| '-' primary
| '~' primary
추상 문법:
MatchValue(matchop op, expr value)
matchop = EqCheck | IdCheck
primary 규칙은 표준 Python 문법에 정의되어 있으며, 단일 토큰으로 구성되거나 닫는 구분자로 끝나야 하는 표현식만 허용합니다.
값 제약은 PEP 634의 리터럴 패턴과 값 패턴을 대체합니다.
동등성 제약은 == EXPR로 작성하고, 동일성 제약은 is EXPR로 작성합니다.
동등성 제약은 대상 값이 오른쪽에 주어진 값과 같다고 비교되면 성공하고, 동일성 제약은 둘이 정확히 같은 객체인 경우에만 성공합니다.
비교 대상이 되는 표현식은 대체로 단일 토큰(예: 이름, 문자열, 숫자, 내장 상수)이나 반드시 닫는 구분자로 끝나야 하는 표현식으로 제한됩니다.
우선순위가 높은 단항 연산자의 사용도 허용됩니다. 모호하게 인식될 위험이 낮고, 괄호 없이 음수를 지정할 수 있는 것이 바람직하기 때문입니다.
같은 match 문에서 같은 제약 표현식이 여러 번 나타나면, 인터프리터는 표현식을 반복해서 평가하는 대신 처음 계산한 값을 캐시하여 재사용할 수 있습니다. (PEP 634 값 패턴과 마찬가지로, 이 캐시는 특정 match 문의 특정 실행 한 번에 엄격하게 묶여 있습니다.)
PEP 634의 리터럴 패턴과 달리, 이 PEP에서는 파서가 복소수 리터럴을 받아들이려면 괄호로 묶어야 합니다. 이 점에 대한 논의는 보류된 아이디어 절을 참고하십시오.
이 PEP가 PEP 634보다 우선하여 채택된다면, 모든 리터럴 패턴과 값 패턴은 대신 값 제약으로 더 명시적으로 작성될 것입니다:
# Literal patterns
match number:
case == 0:
print("Nothing")
case == 1:
print("Just one")
case == 2:
print("A couple")
case == -1:
print("One less than nothing")
case == (1-1j):
print("Good luck with that...")
# Additional literal patterns
match value:
case == True:
print("True or 1")
case == False:
print("False or 0")
case == None:
print("None")
case == "Hello":
print("Text 'Hello'")
case == b"World!":
print("Binary 'World!'")
# Matching by identity rather than equality
SENTINEL = object()
match value:
case is True:
print("True, not 1")
case is False:
print("False, not 0")
case is None:
print("None, following PEP 8 comparison guidelines")
case is ...:
print("May be useful when writing __getitem__ methods?")
case is SENTINEL:
print("Matches the sentinel by identity, not just value")
# Matching against variables and attributes
from enum import Enum
class Sides(str, Enum):
SPAM = "Spam"
EGGS = "eggs"
...
preferred_side = Sides.EGGS
match entree[-1]:
case == Sides.SPAM: # Compares entree[-1] == Sides.SPAM.
response = "Have you got anything without Spam?"
case == preferred_side: # Compares entree[-1] == preferred_side
response = f"Oh, I love {preferred_side}!"
case as side: # Assigns side = entree[-1].
response = f"Well, could I have their Spam instead of the {side} then?"
== preferred_side 예제에 주목하십시오. 제약 표현식에 명시적인 접두 표시를 사용하면 값 조회에 속성이나 리터럴만 사용할 수 있다는 제한이 없어집니다.
== (1-1j) 예제는 괄호를 사용하여 임의의 하위 표현식을 닫힌 표현식으로 바꾸는 방법을 보여 줍니다.
와일드카드 패턴
표면 문법:
wildcard_pattern: "__"
추상 문법:
MatchAlways
와일드카드 패턴은 항상 성공합니다. PEP 634에서와 마찬가지로, 와일드카드 패턴은 어떤 이름도 바인딩하지 않습니다.
PEP 634는 다른 언어와의 일관성을 위해 단일 밑줄을 와일드카드 패턴으로 선택하지만, 이 PEP는 이중 밑줄을 선택합니다. 이중 밑줄은 향후 언어 전체에서 일관되게 만들 수 있는 길이 더 분명한 반면, "_"는 i18n 관련 사용 사례 때문에 그 길이 막혀 있기 때문입니다.
사용 예:
match sequence:
case [__]: # any sequence with a single element
return True
case [start, *__, end]: # a sequence with at least two elements
return start == end
case __: # anything
return False
그룹 패턴
표면 문법:
group_pattern: '(' open_pattern ')'
open_pattern의 문법은 위의 패턴 절을 참고하십시오.
괄호로 묶인 패턴에는 추가 문법이 없으며 추상 문법 트리에 표현되지 않습니다. 이를 통해 사용자는 패턴 주위에 괄호를 추가하여 의도한 그룹화를 강조할 수 있으며, 문법이 닫힌 패턴을 요구하는 곳에서 열린 패턴을 중첩할 수도 있습니다.
PEP 634와 달리, 이 PEP는 모든 시퀀스 패턴을 대괄호로 작성하도록 요구하므로 시퀀스 패턴과의 잠재적인 모호성이 없습니다.
구조적 제약
표면 문법:
structural_constraint:
| sequence_constraint
| mapping_constraint
| attrs_constraint
| class_constraint
참고: 별도의 “구조적 제약” 하위 범주는 추상 문법 트리에서 사용되지 않으며, 표면 문법 정의에서 편의상 그룹으로 묶는 노드로만 사용됩니다.
구조적 제약은 복잡한 객체에 대한 단언을 수행하는 동시에 그 객체에서 값을 추출하는 데 사용되는 패턴입니다.
이 패턴들은 모두 중첩된 AS 패턴을 사용하거나, 패턴 정의에 포함된 pattern_as_clause 요소를 사용하여 여러 값을 바인딩할 수 있습니다.
시퀀스 제약
표면 문법:
sequence_constraint: '[' [sequence_constraint_elements] ']'
sequence_constraint_elements: ','.sequence_constraint_element+ ','?
sequence_constraint_element:
| star_pattern
| simple_pattern
| pattern_as_clause
star_pattern: '*' (pattern_as_clause | wildcard_pattern)
simple_pattern: # Subnode where "as" and "or" patterns must be parenthesised
| closed_pattern
| value_constraint
pattern_as_clause: 'as' pattern_capture_target
추상 문법:
MatchSequence(pattern* patterns)
MatchRestOfSequence(identifier? target)
시퀀스 제약을 사용하면 시퀀스 내의 항목을 검사하고 선택적으로 추출할 수 있습니다.
시퀀스 패턴은 대상 값이 collections.abc.Sequence의 인스턴스가 아니면 실패합니다. 또한 대상 값이 str, bytes 또는 bytearray의 인스턴스인 경우에도 실패합니다(이러한 특수 처리의 필요성을 없앨 가능성에 대한 논의는 보류된 아이디어 절을 참조하십시오).
시퀀스 패턴은 별표 하위 패턴을 최대 하나까지 포함할 수 있습니다. 별표 하위 패턴은 어느 위치에나 올 수 있으며, AST에서는 MatchRestOfSequence 노드로 표현됩니다.
별표 하위 패턴이 없으면 해당 시퀀스 패턴은 고정 길이 시퀀스 패턴이고, 그렇지 않으면 가변 길이 시퀀스 패턴입니다.
고정 길이 시퀀스 패턴은 대상 시퀀스의 길이가 하위 패턴의 개수와 같지 않으면 실패합니다.
가변 길이 시퀀스 패턴은 대상 시퀀스의 길이가 별표가 아닌 하위 패턴의 개수보다 작으면 실패합니다.
대상 시퀀스의 길이는 내장 len() 함수를 사용하여(즉, __len__ 프로토콜을 통해) 얻습니다. 다만 인터프리터는 값 제약 표현식에 대해 설명한 것과 비슷한 방식으로 이 값을 캐시할 수 있습니다.
고정 길이 시퀀스 패턴은 하위 패턴을 대상 시퀀스의 대응하는 항목에 왼쪽에서 오른쪽 순서로 매칭합니다. 하위 패턴 하나가 실패하는 즉시 매칭은 (실패로) 중단됩니다. 모든 하위 패턴이 각자 대응하는 항목과 매칭에 성공하면 시퀀스 패턴이 성공합니다.
가변 길이 시퀀스 패턴은 먼저 앞쪽의 별표 없는 하위 패턴을 고정 길이 시퀀스와 마찬가지로 대상 시퀀스의 대응하는 항목에 매칭합니다. 이것이 성공하면 별표 하위 패턴은 대상의 나머지 항목으로 구성된 리스트에 매칭되며, 이때 별표 하위 패턴 뒤에 오는 별표 없는 하위 패턴에 대응하는 항목은 끝에서 제거됩니다. 그런 다음 나머지 별표 없는 하위 패턴을 고정 길이 시퀀스와 마찬가지로 대응하는 대상 항목에 매칭합니다.
하위 패턴은 대부분 닫힌 패턴이어야 하지만, 값 제약 조건에서는 괄호를 생략할 수 있습니다. 시퀀스 요소는 괄호 없이 무조건 캡처할 수도 있습니다.
참고: PEP 634는 할당문의 이터러블 언패킹과 똑같은 구문상의 유연성을 모두 허용하지만, 이 PEP는 시퀀스 패턴을 대괄호 형식으로만 제한합니다. 이터러블 언패킹에서는 괄호 없는 형식과 괄호로 감싼 형식이 대괄호보다 훨씬 더 널리 쓰인다는 점을 고려하면, 이는 이터러블 언패킹과 시퀀스 매칭이 같은 연산이 아니라는 점을 강조하는 데 도움이 됩니다. 또한 괄호로 감싼 형식에서 발생하는 단일 요소 시퀀스 패턴과 그룹 패턴 사이의 모호성 문제도 피할 수 있습니다.
매핑 제약 조건
표면 문법:
mapping_constraint: '{' [mapping_constraint_elements] '}'
mapping_constraint_elements: ','.key_value_constraint+ ','?
key_value_constraint:
| closed_expr pattern_as_clause
| closed_expr ':' simple_pattern
| double_star_capture
double_star_capture: '**' pattern_as_clause
(추가 매핑 항목은 기본적으로 무시되므로 이 문법에서는 **__를 의도적으로 허용하지 않는다는 점에 유의하십시오)
closed_expr은 앞의 값 제약 조건 항목에서 정의되어 있습니다.
추상 문법:
MatchMapping(expr* keys, pattern* patterns)
매핑 제약 조건을 사용하면 시퀀스 내의 키와 값을 검사하고 선택적으로 값을 추출할 수 있습니다.
대상 값이 collections.abc.Mapping의 인스턴스가 아니면 매핑 패턴은 실패합니다.
매핑 패턴에 주어진 모든 키가 대상 매핑에 존재하고 각 키의 패턴이 대상 매핑의 대응하는 항목에 매칭되면 매핑 패턴이 성공합니다.
키의 존재 여부는 get 메서드의 두 인자 형식과 고유한 센티널 값을 사용하여 검사하며, 이 방식에는 다음과 같은 이점이 있습니다:
- 조회 과정에서 예외를 생성할 필요가 없습니다
__missing__을 구현하는 매핑(collections.defaultdict등)은 이미 포함하고 있는 키에 대해서만 매칭되며, 암묵적으로 키를 추가하지 않습니다
매핑 패턴에는 중복된 키 값이 포함될 수 없습니다. 매핑 패턴을 검사할 때 중복된 키가 발견되면 해당 패턴은 유효하지 않은 것으로 간주되며 ValueError가 발생합니다. 이론적으로는 컴파일 시점에 중복된 상수 키를 검사할 수 있겠지만, 현재 그러한 검사는 정의되거나 구현되어 있지 않습니다.
(참고: 이 의미론적 설명은 PEP 634 참조 구현에서 도출한 것으로, 이 글을 작성하는 시점에서는 PEP 634 명세 본문과 차이가 있습니다. 구현 쪽이 합리적인 것으로 보이므로, PEP 본문을 수정하는 것이 이 불일치를 해소하는 가장 좋은 방법으로 보입니다)
'**' as NAME 이중 별표 패턴이 있으면 그 이름은 대상 매핑의 나머지 키-값 쌍을 모두 담은 dict에 바인딩됩니다(추가 키-값 쌍이 없으면 이 dict는 비어 있습니다).
매핑 패턴에는 이중 별표 패턴이 최대 하나만 포함될 수 있으며, 반드시 마지막에 와야 합니다.
값 하위 패턴은 대부분 닫힌 패턴이어야 하지만, 값 제약 조건에서는 괄호를 생략할 수 있습니다(항목이 일반적인 비교 연산처럼 보이지 않도록 : 키/값 구분자는 여전히 필요합니다).
매핑 값은 괄호나 : 키/값 구분자 없이 KEY as NAME 형식을 사용하여 무조건 캡처할 수도 있습니다.
인스턴스 속성 제약
표면 문법:
attrs_constraint:
| name_or_attr '{' [attrs_constraint_elements] '}'
attrs_constraint_elements: ','.attr_value_pattern+ ','?
attr_value_pattern:
| '.' NAME pattern_as_clause
| '.' NAME value_constraint
| '.' NAME ':' simple_pattern
| '.' NAME
추상 문법:
MatchAttrs(expr cls, identifier* attrs, pattern* patterns)
인스턴스 속성 제약을 사용하면 인스턴스의 타입을 검사하고, 선택적으로 속성을 추출할 수 있습니다.
인스턴스 속성 제약에서는 같은 속성 이름을 여러 번 반복할 수 없습니다. 그렇게 하려고 하면 문법 오류가 발생합니다.
대상이 name_or_attr의 인스턴스가 아니면 인스턴스 속성 패턴은 실패합니다. 이는 isinstance()를 사용하여 검사합니다.
name_or_attr가 내장 type의 인스턴스가 아니면 TypeError가 발생합니다.
속성 하위 패턴이 없으면 isinstance() 검사가 성공할 때 제약이 성공합니다. 그렇지 않으면 다음과 같습니다.
- 주어진 각 속성 이름을 대상의 속성으로 조회합니다.
- 이 조회에서
AttributeError이외의 예외가 발생하면 그 예외는 위로 전파됩니다.- 이 조회에서
AttributeError가 발생하면 제약은 실패합니다.- 그렇지 않으면 키워드에 연결된 하위 패턴을 속성 값과 매칭합니다. 하위 패턴이 지정되지 않은 경우에는 와일드카드 패턴으로 간주합니다. 이 매칭이 실패하면 제약은 실패합니다. 매칭이 성공하면 다음 속성으로 넘어가 매칭을 계속합니다.
- 모든 속성 하위 패턴이 성공하면 제약 전체가 성공합니다.
인스턴스 속성 제약에서 요구하는 인스턴스 타입으로 object를 사용하면 덕 타이핑 검사를 구현할 수 있습니다(예: case object{.host as host, .port as port}:).
여기서 제안하는 문법은 하나의 대입문으로 객체 인스턴스에서 여러 속성을 가져오는 새로운 문법(예: host, port = addr{.host, .port})의 기반으로도 사용될 수 있습니다. 이 점에 대한 자세한 논의는 보류된 아이디어 절을 참조하십시오.
클래스 정의 제약
표면 문법:
class_constraint:
| name_or_attr '(' ')'
| name_or_attr '(' positional_patterns ','? ')'
| name_or_attr '(' class_constraint_attrs ')'
| name_or_attr '(' positional_patterns ',' class_constraint_attrs] ')'
positional_patterns: ','.positional_pattern+
positional_pattern:
| simple_pattern
| pattern_as_clause
class_constraint_attrs:
| '**' '{' [attrs_constraint_elements] '}'
추상 문법:
MatchClass(expr cls, pattern* patterns, identifier* extra_attrs, pattern* extra_patterns)
클래스 정의 제약을 사용하면 관련된 모든 매치 패턴에서 속성 이름을 일일이 지정할 필요 없이, 공통 속성의 시퀀스를 클래스에 지정해 두고 위치 기반으로 검사할 수 있습니다.
인스턴스 속성 패턴과 마찬가지로 다음과 같습니다.
- 대상이
name_or_attr의 인스턴스가 아니면 클래스 정의 패턴은 실패합니다. 이는isinstance()를 사용하여 검사합니다. name_or_attr가 내장type의 인스턴스가 아니면TypeError가 발생합니다.
인자가 있는지 여부와 관계없이, getattr(cls, "__match_args__", _SENTINEL)) 호출과 동등한 방식으로 대상에 __match_args__ 속성이 있는지 검사합니다.
이 과정에서 예외가 발생하면 그 예외는 위로 전파됩니다.
반환된 값이 리스트, 튜플, None 중 하나가 아니면 변환은 실패하고 런타임에 TypeError가 발생합니다.
즉, __match_args__를 실제로 정의하는 타입만 클래스 정의 패턴에서 사용할 수 있습니다. __match_args__를 정의하지 않는 타입도 인스턴스 속성 패턴에서는 여전히 사용할 수 있습니다.
__match_args__가 None이면 위치 하위 패턴은 하나만 허용됩니다. 위치로 지정하든 이중 별표 문법을 사용하든, 추가 속성 패턴을 지정하려고 하면 런타임에 TypeError가 발생합니다.
그런 다음 이 위치 하위 패턴은 대상 전체와 매칭되므로, 타입 검사를 다른 매치 패턴과 결합할 수 있습니다(예: 컨테이너의 타입과 내용을 모두 검사하거나, 숫자의 타입과 값을 모두 검사하는 경우).
__match_args__가 리스트나 튜플이면, 클래스 정의 제약 조건은 다음과 같이 인스턴스 속성 제약 조건으로 변환됩니다:
- 이중 별표 속성 제약 하위 패턴만 있는 경우, 매칭은 그와 동등한 인스턴스 속성 제약과 같은 방식으로 진행됩니다.
__match_args__의 길이(len()으로 얻은 값)보다 위치 하위 패턴이 더 많으면TypeError가 발생합니다.- 그렇지 않으면, 위치 패턴
i는__match_args__[i]를 속성 이름으로 사용하는 속성 패턴으로 변환됩니다. __match_args__의 요소 중 하나라도 문자열이 아니면TypeError가 발생합니다.- 위치 패턴이 속성 패턴으로 변환되고 나면, 이중 별표 속성 제약 하위 패턴에 주어진 속성 제약과 결합되며, 매칭은 그에 해당하는 인스턴스 속성 제약에서처럼 진행됩니다.
참고: 이 PEP의 __match_args__ is None 처리는 PEP 634에서 bool, bytearray, bytes, dict, float, frozenset, int, list, set, str, tuple을 특별하게 취급하던 방식을 대체합니다. 그러나 이 타입들을 위한 최적화된 빠른 경로는 구현에 그대로 유지됩니다.
설계 논의
매치 패턴에서 단순 이름의 명시적 한정 요구
이 PEP의 첫 번째 버전은 이터러블 언패킹 문법이 패턴 매칭을 위한 새로운 문법을 정의하는 데 좋은 토대가 될 것이라는 PEP 634의 기본 전제를 받아들였습니다.
그러나 검토 과정에서 그 핵심 가정으로부터 직접 발생하는 주요 모호성 문제 두 가지와 사소한 모호성 문제 한 가지가 지적되었습니다:
- 가장 큰 문제는, 단순 이름을 기본적으로 바인딩하는 방식을 PEP 634에서 제안한 클래스 패턴 문법으로 확장하면
ATTR=TARGET_NAME구문이as키워드 없이 오른쪽으로 바인딩하며, 게다가 이를 위해 일반적인 왼쪽으로의 대입 기호(=)를 사용한다는 점입니다! - 단순 이름을 기본적으로 바인딩하는 방식을 PEP 634에서 제안한 매핑 패턴 문법으로 확장하면,
KEY: TARGET_NAME구문이as키워드 없이 오른쪽으로 바인딩합니다. - PEP 634 캡처 패턴을 AS 패턴(
TARGET_NAME_1 as TARGET_NAME_2)과 함께 사용하면 “왼쪽과 오른쪽 모두에 바인딩하는” 이상한 동작이 나타납니다.
이 PEP의 세 번째 개정판은 이터러블 언패킹 문법과의 일치를 포기하고, 대신 변수 조회 이외의 용도로 단순 이름을 그대로 쓰는 모든 경우에 앞쪽 기호나 키워드를 붙여 한정하도록 요구함으로써 이 문제를 해결했습니다:
as NAME: 지역 변수 바인딩.NAME: 속성 조회== NAME: 변수 조회is NAME: 변수 조회- 그 밖의 모든 용법: 변수 조회
이 접근 방식의 핵심 이점은 패턴 안의 단순 이름을 국지적으로 해석할 수 있게 한다는 점입니다: 앞에 붙은 as는 이름 바인딩을, 앞에 붙은 .은 속성 조회를 나타내며, 그 밖의 모든 것은 변수 조회입니다(하위 패턴을 읽든 하위 표현식을 읽든 관계없습니다).
이제 이 PEP에서 제안하는 문법을 사용하면, 위에서 지적한 문제 사례들이 더 이상 어색하게 읽히지 않습니다:
.ATTR as TARGET_NAME은ATTR=TARGET_NAME보다 바인딩이라는 것이 더 분명합니다.KEY as TARGET_NAME은KEY: TARGET_NAME보다 바인딩이라는 것이 더 분명합니다.(as TARGET_NAME_1) as TARGET_NAME_2는TARGET_NAME_1 as TARGET_NAME_2보다 두 번의 바인딩이라는 것이 더 분명합니다.
추측하려는 유혹 물리치기
PEP 635는 다른 언어에서 패턴 매칭이 사용되는 방식을 살펴보고, 그 정보를 활용해 Python에서 패턴 매칭이 사용될 방식을 그럴듯하게 예측하려고 시도합니다:
- 지역 이름에 저장된 값과 매칭하려는 경우보다 값을 지역 이름으로 추출하려는 경우가 아마도 더 흔할 것입니다
- 동일성(identity) 비교를 원하는 경우보다 동등성(equality) 비교를 원하는 경우가 아마도 더 흔할 것입니다
- 사용자는 문서를 읽거나 누군가가 알려 주지 않고서는 스스로 알아내지 못하더라도, 맨 이름은 값을 바인딩하고 속성 참조는 값을 조회한다는 점을 적어도 기억할 수는 아마도 있을 것입니다
분명히 말씀드리면, 저는 이러한 예측이 실제로 그럴듯하다고 생각합니다. 하지만 저는 이 문제를 미리 추측할 필요도 없다고 생각합니다: 사용자가 접두 마커(as, ==, is 중 하나)로 의도를 명시하도록 요구하는 더 명시적인 문법으로 시작한 다음, 몇 년 뒤 패턴 매칭이 Python에서 실제로 어떻게 사용되는지를 바탕으로 상황을 재평가할 수 있다고 생각합니다.
그 시점이 되면 적어도 다음과 같은 선택지 중에서 고를 수 있을 것입니다:
- 명시적 문법이 충분히 간결하다고 판단하고 아무것도 변경하지 않음
None,...,True,False중 하나 이상에 대해 추론된 동일성 제약 추가하기- 다른 리터럴(복소수 리터럴을 포함할 수도 있음)에 대해 추론된 동등성 제약 추가하기
- 속성 조회에 대해 추론된 동등성 제약 추가하기
- 맨 이름에 대해 추론된 동등성 제약 또는 추론된 캡처 패턴 추가하기
이러한 아이디어는 모두 애초에 패턴 매칭을 도입하는 데 잠재적인 장벽이 되는 대신, 각각 그 자체의 장점에 따라 독립적으로 검토될 수 있습니다.
이러한 문법적 축약 중 어느 것이 결국 도입되더라도, 그 바탕이 되는 더 명시적인 문법으로 쉽게 설명할 수 있을 것입니다(앞에 붙는 as, ==, is를 사용자가 명시적으로 제공할 필요 없이 파서가 추론하는 것일 뿐입니다). 구현 수준에서는 기존 AST 노드를 재사용할 수 있으므로 파서만 변경하면 될 것입니다.
지역 변수에 속성 조회를 캐싱하는 것과의 상호 작용
이 PEP와 PEP 634 사이의 주요 차이점 중 하나는 NAME.ATTR만 제공하는 대신 동등성 제약 조회에 == EXPR을 사용한다는 점입니다. 원래 동기는 일반 대입 대상과의 의미 충돌을 피하는 것이었습니다. 대입문에서는 이미 NAME.ATTR를 속성 설정에 사용하므로, NAME.ATTR가 기호 값 매칭을 위한 유일한 문법이라면 기존 대입문 문법으로 단일 패턴에 대한 매칭을 허용하려는 향후 시도를 미리 배제하게 됩니다. 현재 동기는 사용자의 의도를 추측하지 않고 대신 문법에서 의도를 명시적으로 밝히도록 요구하려는 일반적인 바람에 더 가깝습니다.
하지만 match 문 자체 안에서도, 값 패턴을 위한 name.attr 문법은 지역 변수 대입과 바람직하지 않은 상호 작용을 일으킵니다. 다른 Python 문에서라면 의미상 중립적이었을 일상적인 리팩터링이 PEP 634 스타일 match 문에 적용되면 큰 의미 변화를 일으킵니다.
다음 코드를 살펴보십시오:
while value < self.limit:
... # Some code that adjusts "value"
속성 조회는 안전하게 루프 밖으로 끌어내어 한 번만 수행할 수 있습니다:
_limit = self.limit:
while value < _limit:
... # Some code that adjusts "value"
이 PEP의 마커 접두사 기반 문법 제안에서는 값 제약도 마찬가지로 속성 조회 대신 지역 변수를 사용하도록 match 패턴을 리팩터링하는 것을 허용하며, 다음 두 문은 기능적으로 동등합니다:
match expr:
case {"key": == self.target}:
... # Handle the case where 'expr["key"] == self.target'
case __:
... # Handle the non-matching case
_target = self.target
match expr:
case {"key": == _target}:
... # Handle the case where 'expr["key"] == self.target'
case __:
... # Handle the non-matching case
반면, 마커 접두사를 생략하는 PEP 634의 값 패턴 및 캡처 패턴 문법을 사용하면 다음 두 문은 전혀 동등하지 않습니다:
# PEP 634's value pattern syntax
match expr:
case {"key": self.target}:
... # Handle the case where 'expr["key"] == self.target'
case _:
... # Handle the non-matching case
# PEP 634's capture pattern syntax
_target = self.target
match expr:
case {"key": _target}:
... # Matches any mapping with "key", binding its value to _target
case _:
... # Handle the non-matching case
이 PEP는 이러한 단순한 방식의 리팩터링에서도 원래 의미가 유지되도록 보장합니다. 결과를 값 제약으로 해석하도록 강제하려면 == name을 사용하고, 이름 바인딩에는 as name을 사용하십시오.
명시적인 접두사 형태 없이 축약 문법만 제공하자는 PEP 634의 제안대로라면, 내놓을 수 있는 주된 답은 “그럼 그렇게 하지 마십시오. 네임스페이스의 속성과만 비교하고 단순 이름과는 비교하지 마십시오”뿐입니다.
PEP 622의 바다코끼리 패턴 문법에는 또 다른 이상한 상호 작용이 있었습니다. case 절 본문에 있는 완전히 같은 바다코끼리 표현식과 같은 객체를 바인딩하지 않을 수도 있다는 점이었습니다. 하지만 PEP 634는 바다코끼리 패턴을 AS 패턴으로 대체하여 이 불일치를 해결했습니다(RHS의 이름에 바인딩되는 값이 LHS가 반환하는 값과 같지 않을 수 있다는 점은 “as” 키워드의 모든 용법에 공통된 표준 특성입니다).
기존 비교 연산자를 값 제약 접두사로 사용하기
전용 값 제약 접두사의 이점을 받아들인다면, 다음 질문은 그 접두사가 정확히 무엇이어야 하는가입니다.
처음 공개된 이 PEP 버전에서는 이전에 사용되지 않던 ? 기호를 동등성 제약의 접두사로, ?is를 동일성 제약의 접두사로 사용할 것을 제안했습니다. PEP를 검토하면서 Steven D’Aprano는 대신 기존 비교 연산자(== 및 is)를 사용하자는 설득력 있는 반대 제안 [5]을 제시했습니다.
접두사로서의 ==에는 몇 가지 우려가 있었고, 그 때문에 PEP의 초기 버전에서는 이것이 접두사로 선택되지 않았습니다:
- 일반적인 사용 사례에서는
?보다도 시각적으로 더 번잡합니다. PEP 8에 익숙한 미적 감각을 지닌 많은 사람이 이 접두사와 뒤따르는 표현식 사이에 공백을 넣고 싶어 할 것이므로, 사실상 1글자가 아닌 3글자 접두사가 되기 때문입니다. - 매핑 패턴에서 사용할 때는
:키/값 구분자와==접두사 사이에 공백이 있어야 합니다. 그렇지 않으면 토크나이저가 이들을 잘못 분리합니다(:와==대신:=와=를 얻게 됩니다). - OR 패턴에서 사용할 때는
|패턴 구분자와==접두사 사이에 공백이 있어야 합니다. 그렇지 않으면 토크나이저가 이들을 잘못 분리합니다(|와==대신|=와=를 얻게 됩니다). - PEP 634 스타일의 클래스 패턴에서 사용한다면
=키워드 구분자와==접두사 사이에 공백이 있어야 합니다. 그렇지 않으면 토크나이저가 이들을 잘못 분리합니다(=와==대신==와=를 얻게 됩니다).
이 장황함 문제에 대해 Steven이 제안한 해결책은 완전히 새로운 기호를 도입하는 대신, 구문적으로 모호하지 않은 경우에는 접두사 표시를 생략할 수 있는 기능을 유지하는 것이었습니다.
접두사 표시를 생략한다는 아이디어는 제안의 두 번째 개정판에서 채택되었지만, 모호성에 대한 우려로 세 번째 개정판에서 다시 제외되었습니다. 그 대신 다음 사항이 적용됩니다.
- 클래스 패턴의 경우, 다른 문법 변경으로 동등 제약을
.ATTR == EXPR로, 동일성 제약을.ATTR is EXPR로 작성할 수 있으며, 둘 다 꽤 읽기 쉽습니다 - 매핑 패턴의 경우, 추가적인 구문상의 번잡함을 (적어도 당분간은) 그냥 감수합니다.
- OR 패턴의 경우, 추가적인 구문상의 번잡함을 (적어도 당분간은) 그냥 감수합니다. 하지만 membership constraints가 향후 OR 패턴과 동등성 제약을 결합할 필요를 줄이는 방법이 될 수 있습니다(그 대신 비교 대상 값들을 집합, 리스트 또는 튜플로 모으게 됩니다).
이러한 관점에서 보면, 패턴 매칭 문법의 일부로 ?를 사용하는 것에 반대하는 PEP 635의 인자들은 이 제안에도 똑같이 적용되므로, 그에 따라 이 PEP를 수정했습니다.
__를 와일드카드 패턴 표시로 사용하기
PEP 635는 ?를 오직 와일드카드 패턴 표시로만 도입하는 것은 좋지 않은 생각이라는 탄탄한 논거를 제시합니다. 값 제약 조건의 문법이 ?와 ?is 대신 기존 비교 연산을 사용하도록 바뀌었으므로, 그 인자는 이 PEP에도 똑같이 적용됩니다.
하지만 Thomas Wouters가 [6]에서 지적했듯이, PEP 634가 _를 선택한 것은 여전히 문제가 있습니다. match 패턴이 Python의 다른 모든 부분과 영구적으로 달라질 가능성이 높기 때문입니다. 소프트웨어 국제화와 대화형 프롬프트에서 _를 사용하므로, 이를 범용 “건너뛴 바인딩” 마커로 사용하는 방향으로 나아갈 그럴듯한 길이 사실상 없습니다.
__는 기존 Python 코드에서 _가 갖는 여러 의미를 다룬 Stack Overflow(스택 오버플로) 답변 [7] (원래 이 PEP의 작성자가 게시한 것)에서 가져온 또 다른 “이 값은 필요하지 않음” 마커입니다.
이 PEP는 또한 __와 관련된 특수 처리의 범위를 파서로 제한하는 구현 기법을 채택할 것을 제안합니다. 즉, __를 Name 노드로 AST에 그대로 전달하는 대신, 와일드카드 표시 전용의 새로운 AST 노드 타입(MatchAlways)을 정의합니다.
파서 내부에서 __는 여전히 파스 트리의 어느 위치에 있느냐에 따라 일반 이름이거나 매치 패턴의 와일드카드 표시를 의미하지만, 컴파일러의 나머지 부분에서 Name("__")는 여전히 일반 변수 이름이고, MatchAlways()는 항상 매치 패턴의 와일드카드 표시입니다.
_와 달리 __에는 다른 사용 사례가 없으므로, Python의 모든 곳에서 __가 “이 이름 바인딩을 건너뜀”을 의미하게 하여 언어의 나머지 부분과 식별자 처리 일관성을 회복하는 그럴듯한 길이 있을 것입니다:
- 인터프리터 자체에서 이름이
__인 변수를 로드하는 것을 폐지 예정으로 지정합니다. 이렇게 하면__에서 읽을 때는 폐지 예정 경고가 발생하지만,__에 쓰는 것은 처음에는 변경되지 않습니다. 모든 이름 로드가 느려지는 것을 피하려면, 표준 이름 로드 옵코드에서 런타임 검사를 사용하는 대신 컴파일러가 폐지 예정인 이름에 대해 추가 코드를 생성하도록 하여 처리할 수 있습니다. - 적절한 수의 릴리스가 지난 후, 파서가 대입 대상으로 쓰이는 모든
__에 대해 새로운SkippedBindingAST 노드를 생성하도록 변경하고, 그에 맞춰 컴파일러의 나머지 부분을 갱신합니다. __를 소프트 키워드가 아닌 진정한 하드 키워드로 만드는 것을 고려합니다.
_에는 이 폐지 경로를 따를 수 없습니다. 인터프리터로서는 명목상 “상관없음” 표시로 사용된 _를 다시 읽으려는 시도와, i18n 텍스트 번역 함수나 대화형 프롬프트의 마지막 문장 결과로서 _를 정당하게 읽는 것을 구별할 방법이 없기 때문입니다.
이중 밑줄로 시작하는 이름은 컴파일 시간 상수(즉, __debug__), 특수 메서드, 클래스 속성 이름 맹글링 등에 쓰이도록 이미 언어에 예약되어 있으므로, 여기서 __를 사용하는 것은 그 기존 접근 방식과 일관됩니다.
추상 문법 트리에서 패턴을 명시적으로 표현하기
PEP 634는 match 문을 추상 문법 트리에서 어떻게 표현해야 하는지 명시적으로 논의하지 않고, 대신 그 세부 사항을 구현의 일부로 정의하도록 남겨 둡니다.
그 결과, PEP 634의 참조 구현은 확실히 동작하지만(그리고 이 PEP의 참조 구현의 기반이 되었지만), 중대한 설계 결함을 안고 있습니다. 패턴은 표현식과 구별되는 것으로 간주해야 한다는 PEP 635의 언급에도 불구하고, 참조 구현은 패턴을 AST에서 표현식 노드로 표현해 버립니다.
그 결과로 나온 AST는 전혀 추상적이지 않습니다. (표현식이 아니라 패턴이기 때문에) 완전히 다르게 컴파일되어야 하는 노드가 같은 방식으로 표현되며, 구현 언어(예: CPython의 경우 C)의 타입 시스템은 어떤 하위 노드가 일반 표현식이어야 하고 어떤 하위 노드가 하위 패턴이어야 하는지 추적하는 데 아무런 도움을 줄 수 없습니다.
이 PEP는 그 접근 방식을 이어 가는 대신 AST에 새로운 명시적 “pattern” 노드를 정의했습니다. 이를 통해 패턴과 패턴에 허용되는 하위 노드를 AST 자체에서 명시적으로 정의할 수 있으므로, 새 기능을 구현하는 코드가 더 명확해지고, 코드 제너레이터가 다루고 있는 것이 패턴인지 표현식인지를 추적하는 데 C 컴파일러가 더 많은 도움을 줄 수 있습니다.
이 구현 방식의 변경은 사실 이 PEP에서 제안하는 표면 문법 변경과는 독립적이므로, PEP의 나머지 부분이 거부되더라도 여전히 채택될 수 있습니다.
시퀀스 패턴의 변경 사항
이 PEP는 PEP 634와 비교하여 시퀀스 패턴에 한 가지 주목할 만한 변경을 가합니다:
- 시퀀스 패턴은 대괄호 형식만 지원합니다. 열린 형식(구분자 없음)이나 튜플 형식(괄호를 구분자로 사용)의 시퀀스 패턴은 지원하지 않습니다.
PEP 634와 비교할 때, 시퀀스 패턴은 캡처 패턴과 값 제약 조건을 명시적으로 한정하도록 요구하는 변경의 영향도 크게 받습니다. 이 변경으로 인해 case [a, b, c]:는 case [as a, as b, as c]:로, case [0, 1]:는 case [== 0, == 1]:로 대신 작성해야 하기 때문입니다.
시퀀스 패턴의 문법이 더 이상 이터러블 언패킹 문법에서 직접 파생되지 않으므로, 원래 문법 제안에서 순전히 이터러블 언패킹과의 일관성을 위해 포함했던 문법적 유연성을 유지하는 것은 더 이상 의미가 없게 되었습니다.
열린 형식과 튜플 형식의 시퀀스 패턴을 허용하는 것은 표현력을 높이지 못하고 (특히 그룹 패턴과 관련하여) 의도의 모호함만 키웠으며, 독자들이 패턴 매칭 문법을 할당 대상 문법과 본질적으로 연결된 것으로 보도록 유도했습니다(PEP 634의 저자들은 이것이 독자들이 택하기를 바라지 않는 방향이라고 여러 차례 밝혔으며, 이 PEP의 저자도 원래는 동의하지 않았지만 이제는 이 견해에 동의합니다).
매핑 패턴의 변경 사항
이 PEP는 PEP 634와 비교하여 매핑 패턴에 두 가지 주목할 만한 변경을 가합니다:
- 값 캡처는
KEY: NAME이 아니라KEY as NAME으로 작성합니다. - 더 넓은 범위의 키가 허용됩니다: 리터럴과 속성 참조뿐 아니라 모든 “닫힌 표현식”이 허용됩니다
앞서 논의했듯이, 첫 번째 변경은 대상 이름이 하위 표현식이나 패턴의 오른쪽에 오는 모든 바인딩 연산이 as 키워드를 사용하도록 보장하기 위한 조치의 일부입니다.
두 번째 변경은 주로 기존의 표현식 처리 메커니즘을 재사용하여 파서와 코드 제너레이터 코드를 단순화하기 위한 것입니다. 닫힌 표현식으로 제한하는 것은 키 표현식이 어디에서 끝나고 매치 패턴이 어디에서 시작하는지에 대한 모호함을 줄이는 데 도움이 되도록 설계되었습니다. 이는 대체로 PEP 634가 허용하는 것의 상위 집합을 허용하지만, 복소수 리터럴은 (적어도 현재로서는) 괄호로 감싸서 작성해야 한다는 점이 다릅니다.
PEP 635의 매핑 패턴 예제를 이 PEP에서 제안하는 문법에 맞게 옮긴 것:
match json_pet:
case {"type": == "cat", "name" as name, "pattern" as pattern}:
return Cat(name, pattern)
case {"type": == "dog", "name" as name, "breed" as breed}:
return Dog(name, breed)
case __:
raise ValueError("Not a suitable pet")
def change_red_to_blue(json_obj):
match json_obj:
case { 'color': (== 'red' | == '#FF0000') }:
json_obj['color'] = 'blue'
case { 'children' as children }:
for child in children:
change_red_to_blue(child)
참고용: 이에 대응하는 PEP 634 문법:
match json_pet:
case {"type": "cat", "name": name, "pattern": pattern}:
return Cat(name, pattern)
case {"type": "dog", "name": name, "breed": breed}:
return Dog(name, breed)
case _:
raise ValueError("Not a suitable pet")
def change_red_to_blue(json_obj):
match json_obj:
case { 'color': ('red' | '#FF0000') }:
json_obj['color'] = 'blue'
case { 'children': children }:
for child in children:
change_red_to_blue(child)
클래스 패턴의 변경 사항
이 PEP는 PEP 634와 비교하여 클래스 패턴에 몇 가지 주목할 만한 변경을 가합니다:
- 클래스 인스턴스 생성과 문법을 맞추는 방식은 적극적으로 오해를 불러일으키고 도움이 되지 않으므로 폐기합니다. 대신, 추가 속성을 검사하기 위한 새로운 전용 문법이 도입되며, 이는 클래스 인스턴스화가 아니라 매핑 패턴에서 착안한 것입니다
- 모든 클래스에서 동작하는 간단한 덕 타이핑을 위한 새로운 전용 문법을 도입합니다.
- 여러 내장 및 표준 라이브러리 타입에 대한 특수 처리는 값이
None인__match_args__속성이 존재하는지에 대한 일반적인 검사로 보완됩니다
앞서 논의했듯이, 첫 번째 변경에는 두 가지 목적이 있습니다:
- 대상 이름이 하위 표현식이나 패턴의 오른쪽에 오는 모든 바인딩 연산이
as키워드를 사용하도록 보장하기 위한 조치의 일부입니다.=를 사용하여 오른쪽으로 할당하는 것은 특히 문제가 됩니다. - 패턴 안의 단순 이름이 쓰이는 모든 곳에 그 목적을 나타내는 접두사가 붙도록 보장하는 일의 일부입니다(이 경우 앞에 붙은
.이 속성 조회를 나타냅니다)
클래스 인스턴스화와 문법을 맞추는 것 역시 전반적으로 도움이 되지 않는다고 판단되었습니다. 클래스 패턴은 속성에 대해 패턴을 매칭하는 것인 반면, 클래스 인스턴스화는 호출 인자를 클래스 생성자의 매개변수에 대응시키는 것이며, 이 매개변수는 결과로 생기는 인스턴스 속성과 전혀 닮지 않았을 수도 있기 때문입니다.
두 번째 변경은 Python에서 이미 흔한 “덕 타이핑” 스타일 검사에 패턴 매칭을 더 쉽게 사용할 수 있도록 하기 위한 것입니다.
이러한 패턴에 대한 구체적인 문법 제안은 인스턴스를 속성 이름에서 값으로의 매핑으로 보고, 속성 조회 문법(.ATTR)과 매핑 패턴 문법 {KEY: PATTERN}을 결합하여 cls{.ATTR: PATTERN}을 만드는 데서 나왔습니다.
cls{.ATTR}가 cls{.ATTR: __}와 같은 의미를 갖도록 허용한 것은, 앞에 붙은 .만으로도 이름 사용이 모호하지 않게 된다고 보았기 때문입니다(이는 분명히 속성 참조인 반면, 매핑 패턴에서 변수 키에 대해 매칭하는 것은 모호하다고 볼 여지가 있습니다)
마지막 변경은 PEP 634 참조 구현에 있는 CPython 내부 전용 검사를 보완할 뿐입니다. 이 검사를 __match_args__를 정의하지 않은 클래스가 갖는 기본 동작으로 만드는 것입니다(PEP 634에 명시된 내장 타입과 표준 라이브러리 타입을 위한 최적화된 빠른 경로는 유지됩니다).
PEP 635에서 링크한 클래스 매칭 예제를 이 PEP에 맞게 바꿔 보면, 순수하게 위치 기반인 클래스 매칭에서는 주된 영향이 클래스 매칭 변경이 아니라 값 제약 조건과 이름 바인딩의 변경에서 온다는 것을 알 수 있습니다:
match expr:
case BinaryOp(== '+', as left, as right):
return eval_expr(left) + eval_expr(right)
case BinaryOp(== '-', as left, as right):
return eval_expr(left) - eval_expr(right)
case BinaryOp(== '*', as left, as right):
return eval_expr(left) * eval_expr(right)
case BinaryOp(== '/', as left, as right):
return eval_expr(left) / eval_expr(right)
case UnaryOp(== '+', as arg):
return eval_expr(arg)
case UnaryOp(== '-', as arg):
return -eval_expr(arg)
case VarExpr(as name):
raise ValueError(f"Unknown value of: {name}")
case float() | int():
return expr
case __:
raise ValueError(f"Invalid expression value: {repr(expr)}")
참고로, 이에 해당하는 PEP 634 문법:
match expr:
case BinaryOp('+', left, right):
return eval_expr(left) + eval_expr(right)
case BinaryOp('-', left, right):
return eval_expr(left) - eval_expr(right)
case BinaryOp('*', left, right):
return eval_expr(left) * eval_expr(right)
case BinaryOp('/', left, right):
return eval_expr(left) / eval_expr(right)
case UnaryOp('+', arg):
return eval_expr(arg)
case UnaryOp('-', arg):
return -eval_expr(arg)
case VarExpr(name):
raise ValueError(f"Unknown value of: {name}")
case float() | int():
return expr
case _:
raise ValueError(f"Invalid expression value: {repr(expr)}")
클래스 패턴 문법 자체의 변경 사항은 __match_args__에 의존하지 않고 이름이 지정된 속성을 검사하고 그 값을 추출할 때 더 중요합니다.:
match expr:
case object{.host as host, .port as port}:
pass
case object{.host as host}:
pass
이를 PEP 634의 동등한 코드와 비교해 보십시오. 거기서는 어떤 이름이 매치 대상의 속성을 가리키고 어떤 이름이 지역 변수를 가리키는지 정말로 분명하지 않습니다.:
match expr:
case object(host=host, port=port):
pass
case object(host=host):
pass
이 특정한 경우에는 그 모호함이 문제가 되지 않지만(속성 이름과 변수 이름이 같기 때문입니다), 일반적인 경우에는 어느 것이 어느 것인지 아는 것이 읽고 있는 코드에 대해 올바르게 추론하는 데 매우 중요합니다.
보류된 아이디어
추론된 값 제약 조건
앞에서 논의한 것처럼, 이 PEP는 추론된 동등성 제약 조건과 동일성 제약 조건을 향후 추가할 가능성을 배제하지 않습니다.
이러한 제약 조건은 리터럴에 특히 유용할 수 있습니다. 의미가 자명한 많은 “매직” 문자열과 숫자가 이름 있는 변수에 저장되지 않고 매치 패턴에 직접 작성될 가능성이 꽤 높기 때문입니다. (None 같은 상수, 0과 1처럼 명백히 특별한 숫자, 또는 내용 자체가 어떤 변수 이름만큼이나 설명적인 문자열을 생각해 보십시오. 739452처럼 의미를 알 수 없는 숫자에 대한 난해한 검사를 말하는 것이 아닙니다)
필수 괄호 일부를 선택 사항으로 만들기
현재 이 PEP는 모호함이 생길 가능성이 있을 때 괄호를 요구하는 쪽으로 크게 치우쳐 있습니다.
그러나 적어도 지나치다고 볼 여지가 있는 경우가 여럿 있으며, 대부분 명시적인 패턴을 가진 AS 패턴과 관련됩니다.
닫힌 패턴이 필요한 모든 위치에서 AS 패턴은 이중 괄호로 시작하게 될 수 있습니다. 중첩된 패턴 역시 닫힌 패턴이어야 하기 때문입니다: ((OPEN PTRN) as NAME)
하위 패턴이 닫혀 있어야 한다는 요구 사항 때문에, 이러한 경우 중 상당수(예: 시퀀스 패턴의 하위 패턴)에서는 CLOSED_PTRN as NAME을 직접 허용하는 것이 합리적입니다.
필수 괄호를 선택 사항으로 만드는 것은 하위 호환성을 유지하는 변경이므로 나중에 제한을 완화하는 것은 사례별로 검토할 수 있습니다. 따라서 이 점에 대한 추가 검토는 보류되었습니다.
복소수 리터럴을 닫힌 표현식으로 허용하기
PEP 634의 참조 구현은 리터럴 값에 대한 임의의 이항 숫자 연산은 허용하지 않으면서 복소수 리터럴은 허용하기 위해, 파서와 나머지 컴파일러 양쪽 모두에서 이항 연산을 특별하게 처리하는 코드를 많이 포함하고 있습니다.
이상적으로는 이 문제를 파서 계층에서 처리하여, 파서가 복소수로 미리 채워진 상수(Constant) AST 노드를 직접 생성하는 것이 바람직합니다. 만약 그런 방식으로 동작한다면, 복소수 리터럴도 다른 모든 리터럴과 비슷한 메커니즘으로 허용할 수 있을 것입니다.
그러나 복소수 리터럴은 그런 방식으로 처리되지 않습니다. 대신 복소수 리터럴은 일반적인 BinOp 노드로 AST에 전달되고, 그 후 AST에 대한 상수 폴딩 단계에서 복소수 값을 가진 Constant 노드로 변환됩니다.
파서가 복소수 리터럴을 직접 변환하려면 컴파일러가 토크나이저에게 허수를 위한 별도의 토큰 타입(예: INUMBER)을 생성하도록 지시할 수 있어야 합니다. 그러면 파서가 NUMBER + INUMBER와 NUMBER - INUMBER를 다른 이항 연산과 별도로 처리할 수 있게 됩니다.
또는 새로운 ComplexNumber AST 노드 타입을 정의할 수도 있습니다. 이렇게 하면 파서가 이후의 컴파일러 단계에 특정 노드가 임의의 이항 연산이 아니라 특별히 복소수 리터럴이어야 한다는 것을 알릴 수 있습니다. 그러면 파서는 해당 노드에 대해 NUMBER + NUMBER와 NUMBER - NUMBER를 허용하고, 리터럴의 실수부와 허수부가 예상대로 실수와 허수인지 확인하는 일은 ComplexNumber에 대한 AST 검증이 맡도록 할 수 있습니다.
현재로서는 이 PEP가 이 문제의 처리를 미루고, 대신 복소수 리터럴을 값 제약 조건이나 매핑 패턴 키로 사용하려면 괄호로 감싸도록 요구하기만 합니다.
매치 패턴에서 부정된 제약 조건 허용하기
이 PEP에서 제안하는 문법으로는 != expr이나 is not expr을 매치 패턴으로 작성할 수 없습니다.
이 두 형태는 각각 부정된 동등성 제약 조건(즉, x != expr)과 부정된 동일성 제약 조건(즉, x is not expr)으로 해석될 수 있다는 점이 분명합니다.
하지만 두 형태 중 어느 것이든 전용 문법을 정당화할 만큼 자주 등장할지는 전혀 분명하지 않으므로, 이 확장 가능성은 match 문에 대한 커뮤니티의 경험이 더 쌓일 때까지 보류되었습니다.
매치 패턴에서 멤버십 검사 허용하기
동등 및 동일성 제약에 사용되는 문법은 멤버십 검사로 쉽게 확장할 수 있을 것입니다: in container.
이 PEP와 PEP 634의 제안에는 모두 한 가지 단점이 있습니다. 같은 case에서 여러 값을 검사하는 방식이 Python에 이미 있는 어떤 컨테이너 멤버십 검사와도 닮지 않았다는 점입니다.:
# PEP 634's literal patterns
match value:
case 0 | 1 | 2 | 3:
...
# This PEP's equality constraints
match value:
case == 0 | == 1 | == 2 | == 3:
...
이 PEP에서 추론된 동등성 제약을 허용하더라도 PEP 634 예제처럼 보이게 될 뿐입니다. 여전히 그에 대응하는 if 문 헤더(if value in {0, 1, 2, 3}:)처럼 보이지는 않습니다.
멤버십 제약은 match 대상이 컨테이너 안에 있는지 검사하는 방법을 제공합니다. 이 방법은 더 명시적이면서도 여전히 간결하며, 일반적인 포함 검사와 똑같은 모습입니다.:
match value:
case in {0, 1, 2, 3}:
...
case in {one, two, three, four}:
...
case in range(4): # It would accept any container, not just literal sets
...
이러한 기능은 또한 사용자 정의 클래스 정의에 __contains__를 적절히 정의하기만 하면, 추가적인 문법 변경 없이 모든 종류의 case 절을 허용하도록 쉽게 확장할 수 있습니다.
하지만 이것은 유용한 확장으로 보이고, OR 패턴에서 여러 동등성 검사를 결합할 때 생기는 이 PEP의 장황함 문제를 해결하는 좋은 방법으로도 보입니다. 그래도 match 문이 언어에 가치 있는 기능이 되는 데 꼭 필요하지는 않습니다. 따라서 여기에 포함하기보다는 별도의 제안으로 미루는 것이 더 적절해 보입니다.
인스턴스 속성 제약의 기본 타입 추론
인스턴스 속성 제약을 위한 전용 문법이 있다는 것은 문법적 모호함을 도입하지 않고도 object{.ATTR}에서 object를 생략하여 {.ATTR}로 쓸 수 있다는 의미입니다(클래스 정의의 베이스 클래스 목록에서와 마찬가지로, 클래스가 주어지지 않으면 object가 암시됩니다).
하지만 여섯 글자를 줄이는 대가로 매핑 패턴과 인스턴스 속성 패턴을 눈으로 구별하기 어려워지는 것이 가치 있는지는 전혀 분명하지 않습니다. 따라서 이를 허용하는 문제는 향후 검토할 주제로 미루었습니다.
시퀀스 패턴에서 특수 사례 피하기
현재 이 PEP와 PEP 634의 시퀀스 패턴은 모두 str, bytes, bytearray를 특수 사례로 취급합니다. 이 타입들은 시퀀스 패턴과 절대 매칭되지 않는다고 명시되어 있습니다.
이러한 타입들은 개념적으로는 단일 항목이지만, 구성 요소에 임의로 접근할 수 있도록 시퀀스 프로토콜을 구현합니다. 이런 타입들을 위해 새로운 collections.abc.AtomicSequence 추상 베이스 클래스를 정의한다면 이 특수 처리를 없앨 수 있을 것입니다.
인스턴스에서 여러 속성을 가져오는 표현식 문법
인스턴스 속성 패턴 문법은 단일 표현식에서 객체의 여러 속성을 가져오는 범용 문법의 기반으로 사용될 수 있도록 설계되었습니다.:
host, port = obj{.host, .port}
슬라이스 문법이 대괄호 첨자 안에서만 허용되는 것과 마찬가지로, 속성 이름을 지정하는 .attr 문법은 중괄호 첨자 안에서만 허용됩니다.
이 아이디어는 패턴 매칭이 유용해지는 데 꼭 필요하지 않으므로 이 PEP에 포함되지 않습니다. 하지만 이 아이디어를 언급하는 이유가 있습니다. 패턴 매칭이 완전히 분리된 자기만의 세계에 영원히 머무르지 않고, 언어의 나머지 부분과 더 잘 통합된 것처럼 느껴지게 하는 한 가지 방향이 될 수 있기 때문입니다.
인스턴스에서 여러 속성을 가져오는 표현식 문법
중괄호 첨자 문법이 인스턴스 속성 패턴 매칭에 채택되고, 이후 여러 속성의 범용 추출을 제공하도록 확장된다면, 매핑 패턴 매칭에 사용되는 문법을 바탕으로 컨테이너에서 여러 항목을 가져올 수 있도록 더 확장할 수도 있습니다.:
host, port = obj{"host", "port"}
first, last = obj{0, -1}
앞에서와 마찬가지로 이 아이디어는 패턴 매칭이 유용해지는 데 꼭 필요하지 않으므로 이 PEP에 포함되지 않습니다. 하지만 여러 속성을 가져오는 경우와 마찬가지로, 제안된 패턴 매칭 문법이 일반적으로 객체 분해를 더 쉽게 만드는 아이디어에 영감을 주는 예시로서 포함하였습니다.
기각된 아이디어
값 제약과 매핑 패턴 키에 허용되는 표현식 제한하기
값 제약과 매핑 패턴 키에 허용되는 표현식을 속성 조회와 상수 리터럴로만 제한하는 것은 기술적으로 충분히 가능합니다(PEP 634가 그렇게 합니다). 하지만 그렇게 해서 런타임에 얻는 분명한 이점은 없습니다. 따라서 이 PEP는 모든 종류의 기본 표현식을 허용하고, 기본 표현식에 대한 높은 우선순위의 단항 연산(+, -, ~)도 허용할 것을 제안합니다(기본 표현식은 문법에 이미 있는 노드 타입으로, 리터럴, 이름, 속성 조회, 함수 호출, 컨테이너 서브스크립트, 괄호로 묶은 그룹 등을 포함합니다).
PEP 635는 리터럴 패턴과 값 패턴이 완전한 표현식이 아니라는 점을 여러 번 강조합니다. 하지만 그 제한으로 얻는 구체적인 이점은 한 번도 제시하지 않습니다(정적 검사와 동적 검사를 분리하면 유용하다는 이론적인 주장만 있을 뿐입니다. 게다가 컴파일러 자체가 더 관대하더라도 코드 스타일 도구로 그러한 분리를 여전히 강제할 수 있습니다).
마지막으로 이러한 제한을 두었던 것은 데코레이터 표현식이었습니다. 그 결과 사용자들은 원하는 동작을 표현하기 위해 수년 동안 어색한 구문적 우회책을 감수해야 했습니다(예를 들어 인자를 그대로 반환하기만 하는 함수 호출 안에 임의의 표현식을 넣었습니다). 이 상황은 언어 정의가 마침내 임의의 표현식을 허용하고 가독성에 대한 판단을 사용자에게 맡기도록 갱신될 때까지 이어졌습니다.
PEP 634에도 데코레이터 표현식의 경우와 닮은 상황이 있습니다. 값 패턴에서 임의의 표현식이 기술적으로는 지원되지만, 어색한 우회책이 필요합니다. 한 가지 방법은 매칭할 모든 값을 match 문 앞에 두는 헬퍼 클래스에 지정하는 것입니다.:
# Allowing arbitrary match targets with PEP 634's value pattern syntax
class mt:
value = func()
match expr:
case (_, mt.value):
... # Handle the case where 'expr[1] == func()'
다른 방법은 캡처 패턴과 가드 표현식을 조합하여 작성하는 것입니다.:
# Allowing arbitrary match targets with PEP 634's guard expressions
match expr:
case (_, _matched) if _matched == func():
... # Handle the case where 'expr[1] == func()'
이 PEP는 이러한 우회책을 요구하지 않고, 처음부터 임의의 값 제약을 지원할 것을 제안합니다.:
match expr:
case (__, == func()):
... # Handle the case where 'expr == func()'
그런 코드를 실제로 작성하는 것이 좋은지는 언어 컴파일러가 아니라 스타일 가이드와 코드 린터가 다룰 주제입니다.
특히 정적 분석기가 특정 종류의 동적 검사를 따라갈 수 없는 경우를 생각해 봅시다. 이때는 컴파일러가 컴파일 시점에 표현식을 제한하는 대신, 정적 분석기가 분석 시점에 허용되는 표현식을 제한할 수 있습니다.
또한 패턴 캐싱 규칙 때문에 거의 확실하게 무의미한 결과를 내는 표현식도 있습니다(예: yield, yield from, await). 이 규칙에서는 제약 표현식이 실제로 평가되는 횟수가 구현에 따라 달라지기 때문입니다. 이런 경우에도 이 PEP는 사용자가 정말 원한다면 무의미한 코드를 작성하도록 허용한다는 입장을 취합니다.
최근에 갱신된 데코레이터 표현식 외에도, Python의 형식 문법이 완전한 표현의 자유를 제공하지만 실제로는 거의 사용되지 않는 또 다른 상황은 except 절입니다. 매치 대상이 되는 예외는 거의 항상 단순한 이름, 점으로 구분된 이름, 또는 이들의 튜플 형태를 띠지만, 언어 문법은 그 자리에 임의의 표현식을 허용합니다. 이는 Python 사용자층이 컴파일러가 허용하더라도 읽기 어려운 구문을 작성하는 것을 피함으로써, 관대한 언어 기능을 읽기 쉬운 방식으로 사용하는 방법을 찾는 책임을 맡을 수 있다고 신뢰할 수 있다는 좋은 지표입니다.
이러한 관대함은 구현 측면에서 실질적이고 구체적인 이점을 가져옵니다. 컴파일러에 있는 match 문 전용 코드 수십 줄이 표현식을 컴파일하는 기존 코드에 대한 간단한 호출로 대체됩니다(AST 검증 패스, AST 최적화 패스, 심볼 테이블 분석 패스, 코드 생성 패스를 모두 포함합니다). 이러한 구현상의 이점은 CPython뿐만 아니라 match 문 지원을 추가하려는 다른 모든 Python 구현에도 돌아갑니다.
매핑 패턴 키에 제약 접두사 표시 사용을 요구하기
이 제안의 초기(미발표) 초안은 PEP 634가 매핑 패턴 키를 유효한 리터럴 패턴 또는 값 패턴으로 요구하는 것과 마찬가지로, 매핑 패턴 키가 값 제약이어야 한다고 요구할 것을 제안했습니다:
import constants
match config:
case {== "route": route}:
process_route(route)
case {== constants.DEFAULT_PORT: sub_config, **rest}:
process_config(sub_config, rest)
그러나 추가 문자는 구문상 번잡했습니다. 또한 값 제약에서 접두사를 쓸 때는 패턴이 아닌 표현식과 구별해 주는 역할을 하지만, 여기서는 매핑 패턴 안에서 키 위치에 있다는 사실만으로 이미 전달되는 정보 외에 추가로 제공하는 정보가 없습니다.
따라서 제안은 매핑 패턴 키에서 표시 접두사를 생략하도록 단순화되었습니다.
이러한 생략은 컨테이너가 조회 과정에 동일성 검사와 동등성 검사를 모두 포함할 수 있다는 사실과도 부합합니다 - 동등성 제약 접두사를 사용하면 컨테이너가 순전히 동등성 검사에만 의존하는 것처럼 잘못 암시하게 되지만, 실제로는 그렇지 않습니다.
매핑 값 제약에 대해 키/값 구분자 생략을 허용하기
인스턴스 속성 패턴에서는 case object{.attr == expr}와 같은 속성 값 제약을 작성할 때 : 구분자를 생략할 수 있습니다.
매핑 값 제약에도 비슷한 단축 표기를 제공하는 방안을 고려했지만, 이를 허용하면 case {0 == 0}:과 같이 몹시 당혹스러운 구문이 가능해집니다. 컴파일러는 이것이 값 제약 == 0을 가진 키 0임을 알지만, 사람 독자에게는 항진적인 비교 연산 0 == 0으로 보입니다. 키/값 구분자를 포함하면 사람 독자에게도 의도가 더 분명해집니다: case {0: == 0}:
참조 구현
이 PEP의 초안 참조 구현 [3]은 Brandt Bucher의 PEP 634 참조 구현 [4]에서 파생되었습니다.
이 PEP의 본문과 비교하면, 초안 참조 구현은 MATCH_CLASS에서 몇몇 내장 타입과 표준 라이브러리 타입을 특수 처리하는 부분을 __match_args__가 None으로 설정되었는지 확인하는 더 일반적인 검사로 아직 보완하지 않았습니다. 또한 클래스 정의 패턴은 현재 __match_args__를 정의하지 않은 클래스도 여전히 허용합니다.
그 밖에 수정된 모든 패턴은 PEP 634가 아니라 이 PEP를 따르도록 갱신되었습니다.
match 패턴의 역파싱(unparsing)은 아직 갱신된 v3 AST로 이전되지 않았습니다.
match 패턴용 AST 검증기는 아직 구현되지 않았습니다.
AST 검증기 전반에 대해서는, 표현식 노드가 기대되는 곳에 표현식 노드만 전달되는지 검사하고 있는지 확인하는 검토가 아직 이루어지지 않았습니다.
이 PEP의 예제는 아직 테스트 케이스로 변환되지 않았으므로, 오타나 기타 오류가 포함되어 있을 가능성이 있습니다.
기존 PEP 634 테스트 중 몇몇은 아직 새로운 SyntaxError 테스트로 변환해야 합니다.
문서는 아직 갱신되지 않았습니다.
감사의 글
PEP 622 및 PEP 634/PEP 635/PEP 636 작성자에게 감사드립니다. 이 PEP의 제안은 단축 버전만 정의하려고 시도하는 것보다 더 명시적인 문법으로 시작하고 특히 흔한 연산을 위한 문법적 단축 표기를 나중에 도입할 수도 있는 편이 더 나은 선택지라고 제안함으로써, 이미 잘 구성된 아이디어의 가독성을 개선하려는 시도에 지나지 않기 때문입니다. 명세에서 두 PEP가 동일한(또는 적어도 매우 유사한) 영역에서는, 의도된 동작을 설명하는 이 PEP의 본문을 PEP 634 본문에서 직접 가져온 경우가 많습니다.
Steven D’Aprano께 감사드립니다. 그는 이 PEP의 핵심 목표를 다음과 같이 달성할 수 있다는 설득력 있는 주장을 펼쳤습니다. “대부분의 사용자가 대부분의 경우에 원할 것”에 대한 우리의 추측은 적어도 일부 사용자에게 적어도 가끔은 틀릴 수밖에 없는데, 그럴 때 컴파일러의 판단을 재정의할 수 있는 능력을 기존 비교 토큰으로 표현합니다. 그리고 PEP 634의 구문 설탕 일부를 (약간 다른 의미 정의로) 유지하여 대부분의 상황에서 PEP 634와 같은 수준의 간결함을 얻습니다. (Paul Sokolosvsky 역시 동등성 제약에 대해 더 이해하기 쉬운 접두사로 ? 대신 ==를 사용할 것을 독립적으로 제안했습니다).
Thomas Wouters에게 감사드립니다. 그가 PEP 640을 발표하고 구조적 패턴 매칭 제안을 공개적으로 검토한 덕분에, 이 PEP의 작성자는 여기서 제안하는 match 패턴 전용 소프트 키워드로 무기한 남아 있는 대신, 향후 PEP가 단순한 이름이 오는 모든 위치에서 항상 참조 바인딩을 건너뛰는 하드 키워드로 바꿀 수 있을 법한 와일드카드 패턴 문법을 계속 주장하기로 마음먹었습니다.
Joao Bueno와 Jim Jewett에게 감사드립니다. 이들은 PEP 작성자가 클래스 패턴과 매핑 패턴 내에서 하위 요소를 캡처하기 위해 제안된 문법(특히 “오른쪽으로 캡처하기”의 문제점)을 더 면밀히 살펴보도록 독려했습니다. 이 검토를 계기로 제안의 v2와 v3 사이에 중대한 변경이 이루어졌습니다.
참고 문헌
부록 A – 전체 문법
다음은 PEP 634의 부록 A를 대체하는 match_stmt의 수정된 전체 문법입니다.
표준 EBNF 외에 사용하는 표기법은 PEP 534를 따릅니다:
'KWD'는 하드 키워드를 나타냅니다"KWD"는 소프트 키워드를 나타냅니다SEP.RULE+는RULE (SEP RULE)*의 축약형입니다!RULE은 부정 전방 탐색 단언입니다
match_stmt: "match" subject_expr ':' NEWLINE INDENT case_block+ DEDENT
subject_expr:
| star_named_expression ',' [star_named_expressions]
| named_expression
case_block: "case" (guarded_pattern | open_pattern) ':' block
guarded_pattern: closed_pattern 'if' named_expression
open_pattern: # Pattern may use multiple tokens with no closing delimiter
| as_pattern
| or_pattern
as_pattern: [closed_pattern] pattern_as_clause
as_pattern_with_inferred_wildcard: pattern_as_clause
pattern_as_clause: 'as' pattern_capture_target
pattern_capture_target: !"__" NAME !('.' | '(' | '=')
or_pattern: '|'.simple_pattern+
simple_pattern: # Subnode where "as" and "or" patterns must be parenthesised
| closed_pattern
| value_constraint
value_constraint:
| eq_constraint
| id_constraint
eq_constraint: '==' closed_expr
id_constraint: 'is' closed_expr
closed_expr: # Require a single token or a closing delimiter in expression
| primary
| closed_factor
closed_factor: # "factor" is the main grammar node for these unary ops
| '+' primary
| '-' primary
| '~' primary
closed_pattern: # Require a single token or a closing delimiter in pattern
| wildcard_pattern
| group_pattern
| structural_constraint
wildcard_pattern: "__"
group_pattern: '(' open_pattern ')'
structural_constraint:
| sequence_constraint
| mapping_constraint
| attrs_constraint
| class_constraint
sequence_constraint: '[' [sequence_constraint_elements] ']'
sequence_constraint_elements: ','.sequence_constraint_element+ ','?
sequence_constraint_element:
| star_pattern
| simple_pattern
| as_pattern_with_inferred_wildcard
star_pattern: '*' (pattern_as_clause | wildcard_pattern)
mapping_constraint: '{' [mapping_constraint_elements] '}'
mapping_constraint_elements: ','.key_value_constraint+ ','?
key_value_constraint:
| closed_expr pattern_as_clause
| closed_expr ':' simple_pattern
| double_star_capture
double_star_capture: '**' pattern_as_clause
attrs_constraint:
| name_or_attr '{' [attrs_constraint_elements] '}'
name_or_attr: attr | NAME
attr: name_or_attr '.' NAME
attrs_constraint_elements: ','.attr_value_constraint+ ','?
attr_value_constraint:
| '.' NAME pattern_as_clause
| '.' NAME value_constraint
| '.' NAME ':' simple_pattern
| '.' NAME
class_constraint:
| name_or_attr '(' ')'
| name_or_attr '(' positional_patterns ','? ')'
| name_or_attr '(' class_constraint_attrs ')'
| name_or_attr '(' positional_patterns ',' class_constraint_attrs] ')'
positional_patterns: ','.positional_pattern+
positional_pattern:
| simple_pattern
| as_pattern_with_inferred_wildcard
class_constraint_attrs:
| '**' '{' [attrs_constraint_elements] '}'
부록 B: 추상 문법 트리(AST) 변경 사항 요약
이 PEP는 AST에 다음과 같은 새 노드를 추가합니다:
stmt = ...
| ...
| Match(expr subject, match_case* cases)
| ...
...
match_case = (pattern pattern, expr? guard, stmt* body)
pattern = MatchAlways
| MatchValue(matchop op, expr value)
| MatchSequence(pattern* patterns)
| MatchMapping(expr* keys, pattern* patterns)
| MatchAttrs(expr cls, identifier* attrs, pattern* patterns)
| MatchClass(expr cls, pattern* patterns, identifier* extra_attrs, pattern* extra_patterns)
| MatchRestOfSequence(identifier? target)
-- A NULL entry in the MatchMapping key list handles capturing extra mapping keys
| MatchAs(pattern? pattern, identifier target)
| MatchOr(pattern* patterns)
attributes (int lineno, int col_offset, int? end_lineno, int? end_col_offset)
matchop = EqCheck | IdCheck
부록 C: PEP 634 대비 변경 사항 요약
전체적인 match/case 문 문법과 가드 표현식 문법은 PEP 634에서와 동일하게 유지됩니다.
이 PEP는 PEP 634와 비교하여 다음과 같은 주요 변경을 합니다:
- 패턴에
expr타입을 재사용하는 대신 AST에 새로운pattern타입을 정의합니다 - 새로운
MatchAs와MatchOrAST 노드를expr타입에서pattern타입으로 옮깁니다 - 와일드카드 패턴이
_(밑줄 하나)에서__(밑줄 두 개)로 바뀌며, AST에 전용MatchAlways노드가 생깁니다 - 의도가 모호하므로 값 패턴과 리터럴 패턴을 제거합니다
- 새로운 표현식 범주인 “닫힌 표현식(closed expressions)”을 도입합니다
- 닫힌 표현식은 기본 표현식이거나, 우선순위가 높은 단항 연산자(
+,-,~) 중 하나가 앞에 붙은 닫힌 표현식입니다 - 새로운 패턴 유형인 “값 제약 패턴(value constraint patterns)”을 도입합니다
- 값 제약은
Constant(리터럴),UnaryOp(음수),BinOp(복소수),Attribute(속성 조회)의 조합을 허용하는 대신 전용MatchValueAST 노드를 가집니다. - 값 제약 패턴은 동등성 제약이거나 동일성 제약입니다
- 동등성 제약은 그 밖에는 임의인 닫힌 표현식 앞에
==를 접두 표지로 붙입니다:== EXPR - 동일성 제약은 그 밖에는 임의인 닫힌 표현식 앞에
is를 접두 표지로 붙입니다:is EXPR - 의도가 모호하기 때문에 캡처 패턴은 제거됩니다. 모든 캡처 연산은 (시퀀스 매칭에서도)
as키워드를 사용하며, AST에서는MatchAs또는MatchRestOfSequence노드로 표현됩니다. - AS 패턴의 장황함을 줄이기 위해
as NAME을 허용하며, 이는__ as NAME과 같은 의미입니다. - 시퀀스 패턴은 할당 대상과 동일한 문법적 유연성을 제공하는 대신 대괄호 사용을 요구하도록 변경됩니다(할당문에서는 괄호나 대괄호로 둘러싸든 그렇지 않든, 튜플 형태로 구분된 대상을 어떤 식으로든 사용하면 이터러블 언패킹을 나타낼 수 있습니다).
- 시퀀스 패턴은
List를 재사용하는 대신 전용MatchSequenceAST 노드를 갖게 됩니다. - 매핑 패턴은 임의의 닫힌 표현식을 키로 허용하도록 변경됩니다.
- 매핑 패턴은
Dict를 재사용하는 대신 전용MatchMappingAST 노드를 갖게 됩니다. - 매핑 패턴의 장황함을 줄이기 위해
KEY : __ as NAME을KEY as NAME으로 줄여 쓸 수 있습니다. - 클래스 패턴은 더 이상 속성 매칭에 개별 키워드 인자 문법을 사용하지 않습니다. 대신 이중 별표 문법과 함께, 속성 이름 앞에 점 접두사를 붙인 매핑 패턴 문법의 변형을 사용합니다.
- 클래스 패턴은
Call을 재사용하는 대신 전용MatchClassAST 노드를 갖게 됩니다. - 장황함을 줄이기 위해, 클래스 속성 매칭에서는 매칭할 패턴이
==,is,as중 하나로 시작하면:기호를 생략할 수 있습니다. - 클래스 패턴은
__match_args__를None으로 설정한 모든 클래스를, 객체 전체와 매칭되는 단일 위치 패턴을 받는 것으로 취급합니다(PEP 634에서 필요했던 특수 처리를 피합니다). - 클래스 패턴은
__match_args__를 정의하지 않은 객체와 함께 사용되면TypeError를 발생시킵니다. - 덕 타이핑을 위한 전용 문법이 추가되어,
case cls{...}:형태는case cls(**{...}):형태와 대략 동등하지만__match_args__의 존재 여부 검사는 건너뜁니다. 이 패턴 역시 전용 AST 노드인MatchAttrs를 갖습니다.
리터럴 패턴을 뒤로 미루면 허수 리터럴을 위해 토크나이저에 “INUMBER” 토큰이 필요한지에 대한 질문도 뒤로 미룰 수 있다는 점에 유의하십시오. 그 토큰이 없으면 파서는 복소수 리터럴을 상수에 대한 다른 이항 덧셈 및 뺄셈 연산과 구별할 수 없으므로, PEP 634와 같은 제안은 올바른 사용인지 확인하기 위해 이후 컴파일 단계에서 추가 작업을 해야 합니다.
부록 D: 이 제안의 변경 이력
이 제안의 첫 번째 공개 버전은 대체로 PEP 634를 따랐지만, PEP 634의 값 패턴과 리터럴 패턴 대신 동등성 제약에는 ?EXPR를, 동일성 제약에는 ?is EXPR를 사용할 것을 제안했습니다.
두 번째 공개 버전은 Steven D’Aprano의 대안 제안을 대체로 채택하여, 많은 상황에서 PEP 634 스타일의 추론된 제약을 유지하면서도 명시적 동등성 제약에는 == EXPR를, 명시적 동일성 제약에는 is EXPR를 사용할 수 있도록 했습니다.
세 번째 공개 버전(현재 버전)은 추론된 패턴을 완전히 제거했는데, 이는 case {key: NAME}: 패턴과 case cls(attr=NAME): 패턴이 as 키워드를 사용하지 않은 채 NAME이 다른 하위 표현식의 오른쪽에 나타나는데도 둘 다 NAME을 바인딩한다는 사실에 대한 우려를 해소하기 위한 시도였습니다. 수정된 제안은 또한 주어진 두 이름 모두에 바인딩하게 되는 case TARGET1 as TARGET2: 형태를 작성할 가능성도 없앱니다. 이러한 변경 사항 중 가장 우려스러웠던 것은 case cls(attr=TARGET_NAME): 형태였는데, 바인딩 대상을 오른쪽에 두고 = 기호를 사용하는 방식이어서 할당문, 함수 호출, 함수 시그니처 선언에서 일어나는 것과 정반대였기 때문입니다.
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.