PEP 653 – 패턴 매칭의 정확한 의미론
- Author:
- Mark Shannon <mark at hotpy.org>
- Status:
- Draft
- Type:
- Standards Track
- Created:
- 09-Feb-2021
- Post-History:
- 18-Feb-2021
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
이 PEP는 PEP 634의 일반적인 개념을 따르면서도 더 정확하고, 추론하기 쉬우며, 더 빠를 것으로 예상되는 패턴 매칭의 의미론을 제안합니다.
패턴 매칭을 지원하기 위해 객체 모델은 PEP 634의 __match_args__속성에 더해 두 개의 특수 (더더) 속성인 __match_container__ 및 __match_class__로 확장됩니다. 이 새로운 두 속성은 모두 정수여야 하며, __match_args__는 고유한 문자열로 이루어진 튜플이어야 합니다.
이 PEP에서는 다음과 같습니다.
- 패턴 매칭의 의미론이 더 명확해져 패턴을 더 쉽게 추론할 수 있습니다.
- 패턴 매칭을 더 효율적인 방식으로 구현할 수 있습니다.
- 클래스가 어떤 패턴과 매칭할지 더 많이 제어할 수 있으므로, 복잡한 클래스에서 패턴 매칭을 더 유용하게 사용할 수 있습니다.
동기
Python의 패턴 매칭은 PEP 634에서 설명한 대로 Python 3.10에 추가될 예정입니다. 안타깝게도 PEP 634는 의미론을 가능한 만큼 정확하게 기술하지 않으며, 클래스가 패턴과 매칭하는 방식을 충분히 제어할 수 있도록 허용하지도 않습니다.
정확한 의미론
PEP 634는 정의되지 않은 동작에 관한 절을 명시적으로 포함합니다. C와 같은 언어에서는 정의되지 않은 동작이 많이 허용될 수 있지만, Python에서는 이를 최소한으로 유지해야 합니다. Python의 패턴 매칭은 표현력이나 성능을 잃지 않으면서 더 정확하게 정의할 수 있습니다.
클래스 매칭에 대한 향상된 제어
PEP 634는 클래스가 시퀀스인지 매핑인지에 대한 결정을 collections.abc에 위임합니다. 시퀀스로 간주할 수 있는 모든 클래스가 collections.abc.Sequence의 서브클래스로 등록되어 있는 것은 아닙니다. 이 PEP를 사용하면 이러한 클래스가 collections.abc.Sequence의 전체 기능을 사용하지 않고도 시퀀스 패턴과 매칭할 수 있습니다.
PEP 634는 일부 내장 클래스에 “self” 매칭이라는 특별한 형태의 매칭을 허용합니다. 예를 들어 list(x) 패턴은 리스트와 매칭하고 해당 리스트를 x에 할당합니다. 클래스가 어떤 종류의 패턴과 매칭할지 선택하도록 허용하면 다른 클래스도 이 형식을 사용할 수 있습니다.
예를 들어 sympy를 사용하여 다음과 같이 작성하고 싶을 수 있습니다.:
# a*a == a**2
case Mul(args=[Symbol(a), Symbol(b)]) if a == b:
return Pow(a, 2)
이를 위해서는 sympy 클래스 Symbol이 “self” 매칭을 수행해야 합니다. sympy가 PEP 634을 사용하여 이 패턴을 지원하는 것은 가능하지만 다소 까다롭습니다. 이 PEP를 사용하면 이를 매우 쉽게 구현할 수 있습니다 [1].
견고성
이 PEP를 사용하면 패턴 매칭 중 속성에 대한 접근이 잘 정의되고 결정론적으로 이루어집니다. 이는 객체-관계 매퍼와 같이 숨겨진 부작용이 있는 객체를 매칭할 때 패턴 매칭에서 오류가 발생할 가능성을 줄입니다. 객체는 자체 분해를 더 많이 제어할 수 있으며, 이를 통해 속성 접근에 부작용이 있을 때 의도하지 않은 결과가 발생하는 것을 방지할 수 있습니다.
PEP 634는 값이 일치할 수 있는 패턴을 결정할 때 collections.abc 모듈에 의존하며, 필요한 경우 이를 암묵적으로 가져옵니다. 이 PEP는 이러한 가져오기로 인해 발생하는 놀라운 임포트 오류와 오해를 불러일으키는 감사 이벤트를 제거합니다.
효율적인 구현
이 PEP에서 제안하는 의미론은 정밀한 의미론을 사용하고 객체 모델을 사용한 결과로 효율적인 구현을 가능하게 합니다.
정밀한 의미론을 사용하면 어떤 코드 변환이 올바른지 추론할 수 있으므로 최적화를 효과적으로 적용할 수 있습니다.
객체 모델은 Python의 핵심 부분이므로 구현에서는 이미 특수 속성 조회를 효율적으로 처리합니다. 특수 속성을 조회하는 것이 추상 베이스 클래스에서 서브클래스 테스트를 수행하는 것보다 훨씬 빠릅니다.
근거
객체 모델과 특수 메서드는 Python 언어의 핵심입니다. 따라서 구현에서는 이를 잘 지원합니다. 패턴 매칭에 특수 속성을 사용하면 나머지 구현과 잘 통합되는 방식으로 패턴 매칭을 구현할 수 있으므로 유지 관리가 더 쉽고 성능도 더 우수할 가능성이 있습니다.
match 문은 일련의 패턴 매칭을 수행합니다. 일반적으로 패턴을 매칭하는 과정에는 세 부분이 있습니다.
- 값이 이 종류의 패턴과 일치할 수 있습니까?
- 분해했을 때 값이 이 특정 패턴과 일치합니까?
- 가드가 참입니까?
값이 특정 종류의 패턴과 일치할 수 있는지 결정하기 위해 __match_container__ 및 __match_class__ 속성을 추가합니다. 이를 통해 값의 종류를 효율적인 방식으로 결정할 수 있습니다.
사양
객체 모델에 대한 추가 사항
__match_container__ 및 __match_class__ 속성이 object에 추가됩니다. 매핑 또는 시퀀스 패턴과 일치시키려는 클래스는 __match_container__를 재정의해야 합니다. 클래스 패턴을 매칭할 때 기본 동작을 변경하려는 클래스는 __match_class__를 재정의해야 합니다.
__match_container__는 정수여야 하며 다음 중 정확히 하나여야 합니다.:
0
MATCH_SEQUENCE = 1
MATCH_MAPPING = 2
MATCH_SEQUENCE는 해당 클래스의 인스턴스가 시퀀스 패턴과 일치할 수 있음을 나타내는 데 사용됩니다.
MATCH_MAPPING은 해당 클래스의 인스턴스가 매핑 패턴과 일치할 수 있음을 나타내는 데 사용됩니다.
__match_class__는 정수여야 하며 다음 중 정확히 하나여야 합니다.:
0
MATCH_SELF = 8
MATCH_SELF는 단일 위치 인자 클래스 패턴의 경우 대상이 사용되고 분해되지 않음을 나타내는 데 사용됩니다.
Note
이 문서의 나머지 부분에서는 위 값을 이름으로만 참조합니다. 기호 상수는 Python과 C 모두에 제공되며, 값은 절대 변경되지 않습니다.
object는 특수 속성에 대해 다음 값을 갖습니다.:
__match_container__ = 0
__match_class__= 0
__match_args__ = ()
이러한 특수 속성은 일반적인 방식으로 상속됩니다.
__match_args__를 재정의하는 경우 고유한 문자열로 이루어진 튜플을 포함해야 합니다. 비어 있을 수도 있습니다.
Note
__match_args__는 PEP 634에 명시된 대로 데이터 클래스와 명명된 튜플에 대해 자동으로 생성됩니다.
패턴 매칭 구현은 이러한 속성이 지정된 대로 동작하는지 확인할 필요가 없습니다. __match_container__, __match_class__ 또는 __match_args__의 값이 명시된 대로가 아니면, 구현은 어떤 예외든 발생시키거나 잘못된 패턴과 매칭할 수 있습니다. 물론 구현은 효율적으로 수행할 수 있다면 이러한 속성을 확인하고 의미 있는 오류 메시지를 제공할 수 있습니다.
매칭 과정의 의미론
다음에서 $var 형식의 모든 변수는 임시 변수이며 Python 프로그램에서 볼 수 없습니다. 이러한 변수는 인트로스펙션을 통해 볼 수 있을 수 있지만, 이는 구현 세부 사항이므로 이에 의존해서는 안 됩니다. 의사 문장 FAIL은 이 패턴에 대한 매칭이 실패했으며 다음 패턴으로 진행해야 함을 나타내는 데 사용됩니다. FAIL에 도달하지 않고 제어 흐름이 변환의 끝에 도달하면 매칭된 것이며, 이후 패턴은 무시됩니다.
$ALL_CAPS 형식의 변수는 구문 요소를 담는 메타 변수이며, 일반 변수가 아닙니다. 따라서 $VARS = $items는 $items를 $VARS에 할당하는 것이 아니라, $items를 $VARS가 담고 있는 변수들로 언패킹하는 것입니다. 예를 들어 추상 구문이 case [$VARS]:이고 구체 구문이 case[a, b]:라면, $VARS는 해당 변수들의 값이 아니라 변수 (a, b)를 담습니다.
의사 함수 QUOTE는 변수를 받아 해당 변수의 이름을 반환합니다. 예를 들어 메타 변수가 $VAR가 변수 foo를 담고 있었다면 QUOTE($VAR) == "foo"가 됩니다.
원본 소스에 존재하지 않는 아래의 모든 추가 코드는 PEP 626에 부합하여 줄 이벤트를 발생시키지 않습니다.
서문
어떤 패턴이든 매칭되기 전에 매칭할 표현식이 평가됩니다.:
match expr:
다음으로 변환됩니다.:
$value = expr
캡처 패턴
캡처 패턴은 항상 매칭되므로, 반박 불가능한 매칭은:
case capture_var:
다음으로 변환됩니다.:
capture_var = $value
와일드카드 패턴
와일드카드 패턴은 항상 매칭되므로,:
case _:
다음으로 변환됩니다.:
# No code -- Automatically matches
리터럴 패턴
리터럴 패턴은:
case LITERAL:
다음으로 변환됩니다.:
if $value != LITERAL:
FAIL
단, 리터럴이 None, True 또는 False , 중 하나인 경우에는 다음으로 변환됩니다.:
if $value is not LITERAL:
FAIL
값 패턴
값 패턴은:
case value.pattern:
다음으로 변환됩니다.:
if $value != value.pattern:
FAIL
시퀀스 패턴
스타 패턴을 포함하지 않는 패턴:
case [$VARS]:
다음으로 변환됩니다.:
$kind = type($value).__match_container__
if $kind != MATCH_SEQUENCE:
FAIL
if len($value) != len($VARS):
FAIL
$VARS = $value
예: [2]
스타 패턴을 포함하는 패턴:
case [$VARS]
다음으로 변환됩니다.:
$kind = type($value).__match_container__
if $kind != MATCH_SEQUENCE:
FAIL
if len($value) < len($VARS):
FAIL
$VARS = $value # Note that $VARS includes a star expression.
예: [3]
매핑 패턴
더블 스타 패턴을 포함하지 않는 패턴:
case {$KEYWORD_PATTERNS}:
다음으로 변환됩니다.:
$sentinel = object()
$kind = type($value).__match_container__
if $kind != MATCH_MAPPING:
FAIL
# $KEYWORD_PATTERNS is a meta-variable mapping names to variables.
for $KEYWORD in $KEYWORD_PATTERNS:
$tmp = $value.get(QUOTE($KEYWORD), $sentinel)
if $tmp is $sentinel:
FAIL
$KEYWORD_PATTERNS[$KEYWORD] = $tmp
예: [4]
더블 스타 패턴을 포함하는 패턴:
case {$KEYWORD_PATTERNS, **$DOUBLE_STARRED_PATTERN}:
다음으로 변환됩니다.:
$kind = type($value).__match_container__
if $kind != MATCH_MAPPING:
FAIL
# $KEYWORD_PATTERNS is a meta-variable mapping names to variables.
$tmp = dict($value)
if not $tmp.keys() >= $KEYWORD_PATTERNS.keys():
FAIL:
for $KEYWORD in $KEYWORD_PATTERNS:
$KEYWORD_PATTERNS[$KEYWORD] = $tmp.pop(QUOTE($KEYWORD))
$DOUBLE_STARRED_PATTERN = $tmp
예: [5]
클래스 패턴
인수가 없는 클래스 패턴:
case ClsName():
다음으로 변환됩니다.:
if not isinstance($value, ClsName):
FAIL
단일 위치 패턴을 포함하는 클래스 패턴:
case ClsName($VAR):
다음으로 변환됩니다.:
$kind = type($value).__match_class__
if $kind == MATCH_SELF:
if not isinstance($value, ClsName):
FAIL
$VAR = $value
else:
As other positional-only class pattern
위치 전용 클래스 패턴:
case ClsName($VARS):
다음으로 변환됩니다.:
if not isinstance($value, ClsName):
FAIL
$attrs = ClsName.__match_args__
if len($attr) < len($VARS):
raise TypeError(...)
try:
for i, $VAR in enumerate($VARS):
$VAR = getattr($value, $attrs[i])
except AttributeError:
FAIL
예: [6]
모든 키워드 패턴을 포함하는 클래스 패턴:
case ClsName($KEYWORD_PATTERNS):
다음으로 변환됩니다.:
if not isinstance($value, ClsName):
FAIL
try:
for $KEYWORD in $KEYWORD_PATTERNS:
$tmp = getattr($value, QUOTE($KEYWORD))
$KEYWORD_PATTERNS[$KEYWORD] = $tmp
except AttributeError:
FAIL
예: [7]
위치 패턴과 키워드 패턴을 포함하는 클래스 패턴:
case ClsName($VARS, $KEYWORD_PATTERNS):
다음으로 변환됩니다.:
if not isinstance($value, ClsName):
FAIL
$attrs = ClsName.__match_args__
if len($attr) < len($VARS):
raise TypeError(...)
$pos_attrs = $attrs[:len($VARS)]
try:
for i, $VAR in enumerate($VARS):
$VAR = getattr($value, $attrs[i])
for $KEYWORD in $KEYWORD_PATTERNS:
$name = QUOTE($KEYWORD)
if $name in pos_attrs:
raise TypeError(...)
$KEYWORD_PATTERNS[$KEYWORD] = getattr($value, $name)
except AttributeError:
FAIL
예: [8]
중첩된 패턴
위 사양에서는 패턴이 중첩되지 않는다고 가정합니다. 중첩된 패턴의 경우에는 임시 캡처 패턴을 도입하여 위 변환을 재귀적으로 적용합니다.
예를 들어, 패턴:
case [int(), str()]:
다음으로 변환됩니다.:
$kind = type($value).__match_class__
if $kind != MATCH_SEQUENCE:
FAIL
if len($value) != 2:
FAIL
$value_0, $value_1 = $value
#Now match on temporary values
if not isinstance($value_0, int):
FAIL
if not isinstance($value_1, str):
FAIL
가드
가드는 번역의 나머지 부분 다음에 오는 테스트로 번역됩니다:
case pattern if guard:
다음으로 번역됩니다:
[translation for pattern]
if not guard:
FAIL
사양을 따르지 않는 특수 속성
모든 클래스는 __match_container__, __match_class__ 및 __match_args__의 값이 명세를 따르도록 보장해야 합니다. 따라서 구현에서는 다음 사항이 참이라고 확인하지 않고 가정할 수 있습니다:
__match_container__ == 0 or __match_container__ == MATCH_SEQUENCE or __match_container__ == MATCH_MAPPING
__match_class__ == 0 or __match_class__ == MATCH_SELF
또한 __match_args__가 중복되지 않는 문자열의 튜플이라고 가정할 수 있습니다.
표준 라이브러리 클래스의 특수 속성 값
핵심 내장 컨테이너 클래스에서 __match_container__는 다음과 같습니다.
list:MATCH_SEQUENCEtuple:MATCH_SEQUENCEdict:MATCH_MAPPINGbytearray: 0bytes: 0str: 0
네임드 튜플의 __match_container__는 MATCH_SEQUENCE로 설정됩니다.
issubclass(cls, collections.abc.Mapping)이 참인 다른 모든 표준 라이브러리 클래스의__match_container__는MATCH_MAPPING으로 설정됩니다.issubclass(cls, collections.abc.Sequence)이 참인 다른 모든 표준 라이브러리 클래스의__match_container__는MATCH_SEQUENCE로 설정됩니다.
다음 내장 클래스의 __match_class__는 MATCH_SELF로 설정됩니다.
boolbytearraybytesfloatfrozensetintsetstrlisttupledict
허용되는 최적화
위 의미 체계는 구현에서 많은 중복 작업과 복사를 필요로 합니다. 그러나 순진한 구현에 의미를 보존하는 변환을 적용하면 위 의미 체계를 효율적으로 구현할 수 있습니다.
매칭을 수행할 때 구현은 다음 함수와 메서드를 순수한 것으로 취급할 수 있습니다.
MATCH_SEQUENCE를 지원하는 모든 클래스의 경우:
* ``cls.__len__()``
* ``cls.__getitem__()``
MATCH_MAPPING을 지원하는 모든 클래스의 경우:
* ``cls.get()`` (두 인자 형식만 해당합니다)
구현은 다음과 같은 가정을 할 수 있습니다.
isinstance(obj, cls)는issubclass(type(obj), cls)로, 그 반대로도 자유롭게 대체할 수 있습니다.isinstance(obj, cls)는 어떤(obj, cls)쌍에 대해서도 항상 동일한 결과를 반환하므로 반복 호출을 생략할 수 있습니다.__match_container__,__match_class__또는__match_args__중 어느 것이든 읽는 것은 순수한 연산이며 캐시할 수 있습니다.- 시퀀스, 즉
__match_container__ == MATCH_SEQUENCE이 0이 아닌 모든 클래스는 반복, 첨자 사용 또는len()호출에 의해 수정되지 않습니다. 따라서 불변 시퀀스에 적용했을 때 서로 동등한 경우에는 해당 연산들을 서로 자유롭게 대체할 수 있습니다. - 매핑, 즉
__match_container__ == MATCH_MAPPING이 0이 아닌 모든 클래스는get()메서드의 두 번째 인자를 캡처하지 않습니다. 따라서$sentinel값은 자유롭게 재사용할 수 있습니다.
실제로 구현에서는 이러한 가정을 사용하는 것이 권장됩니다. 이는 성능이 크게 향상될 가능성이 높기 때문입니다.
보안 관련 사항
없음
구현
사양에서 파생되는 순진한 구현은 그다지 효율적이지 않습니다. 다행히 성능을 개선하는 데 사용할 수 있는 상당히 간단한 변환들이 있습니다. 성능은 3.10 릴리스 시점에 PEP 634의 구현과 비슷한 수준이어야 합니다. 추가적인 성능 개선은 3.11 릴리스까지 기다려야 할 수 있습니다.
가능한 최적화
다음 내용은 사양의 일부가 아니라, 개발자가 효율적인 구현을 만들 수 있도록 돕는 지침입니다.
평가를 레인으로 분할하기
각 패턴을 매칭할 때 첫 단계가 종류를 확인하는 것이므로, 종류에 대한 모든 확인을 매치 시작 부분의 하나의 다중 분기문으로 결합할 수 있습니다. 그런 다음 사례 목록을 각각 하나의 종류에 대응하는 여러 “레인”으로 복제할 수 있습니다. 그러면 각 레인에서 매칭할 수 없는 사례를 제거하는 작업은 간단합니다. 종류에 따라 각 레인에 서로 다른 최적화 전략을 사용할 수 있습니다. match 절의 본문은 복제할 필요가 없고 패턴만 복제하면 된다는 점에 유의하십시오.
시퀀스 패턴
이는 최적화하기에 가장 복잡하고 성능 측면에서 가장 큰 이득을 얻을 수 있는 부분일 것입니다. 각 패턴은 길이 범위(대개 단일 길이)만 매칭할 수 있으므로, 테스트 시퀀스를 시퀀스에 대한 명시적 반복으로 다시 작성하여 해당 시퀀스 길이에 적용되는 패턴만 매칭하도록 시도할 수 있습니다.
예를 들어 다음과 같습니다.
case []:
A
case [x]:
B
case [x, y]:
C
case other:
D
대략 다음과 같이 컴파일할 수 있습니다.
# Choose lane
$i = iter($value)
for $0 in $i:
break
else:
A
goto done
for $1 in $i:
break
else:
x = $0
B
goto done
for $2 in $i:
del $0, $1, $2
break
else:
x = $0
y = $1
C
goto done
other = $value
D
done:
매핑 패턴
여기서 가장 좋은 전략은 매핑의 크기와 어떤 키가 존재하는지를 기반으로 결정 트리를 구성하는 것일 것입니다. 키의 존재 여부를 반복해서 테스트하는 것은 의미가 없습니다. 예를 들어 다음과 같습니다.:
match obj:
case {a:x, b:y}:
W
case {a:x, c:y}:
X
case {a:x, b:_, c:y}:
Y
case other:
Z
X 사례를 확인할 때 키 "a"가 없었다면 Y에 대해 다시 확인할 필요가 없습니다.
매핑 경로는 대략 다음과 같이 구현할 수 있습니다.
# Choose lane
if len($value) == 2:
if "a" in $value:
if "b" in $value:
x = $value["a"]
y = $value["b"]
goto W
if "c" in $value:
x = $value["a"]
y = $value["c"]
goto X
elif len($value) == 3:
if "a" in $value and "b" in $value:
x = $value["a"]
y = $value["c"]
goto Y
other = $value
goto Z
이 PEP와 PEP 634의 차이점 요약
의미론의 변경 사항은 다음과 같이 요약할 수 있습니다.
__match_args__는 단순한 시퀀스가 아니라 문자열의 튜플이어야 합니다.__match_args__가 변경 불가능하다고 가정할 수 있으므로, 이를 통해 패턴 매칭이 더 견고하고 최적화하기 쉬워집니다.- 매칭할 수 있는 컨테이너 패턴의 종류를 선택할 때는
issubclass(cls, collections.abc.Mapping)및issubclass(cls, collections.abc.Sequence)대신cls.__match_container__를 사용합니다. - 필요한 경우
__match_class__ = 0으로 설정하여 클래스가 구조 분해를 완전히 선택 해제할 수 있도록 합니다. - 패턴 매칭 시의 동작을 더 정확하게 정의했지만, 그 밖에는 변경되지 않았습니다.
구문 변경은 없습니다. 제시된 모든 예제는 PEP 636 튜토리얼에서 현재와 동일하게 계속 작동해야 합니다.
거부된 아이디어
인스턴스의 딕셔너리에서 속성 사용
이 PEP의 초기 버전에서는 __match_class__가 기본값일 때 클래스 패턴을 매칭하면서 인스턴스의 딕셔너리에 있는 속성만 사용했습니다. 이는 바운드 메서드와 기타 합성 속성을 캡처하지 않기 위한 것이었습니다. 그러나 이로 인해 프로퍼티도 무시되었습니다.
다음 클래스의 경우:
class C:
def __init__(self):
self.a = "a"
@property
def p(self):
...
def m(self):
...
이상적으로는 속성 “a”와 “p”는 매칭하고 “m”은 매칭하지 않아야 합니다. 그러나 이를 수행할 일반적인 방법이 없으므로, 이 PEP는 이제 PEP 634의 의미론을 따릅니다.
패턴이 아니라 대상에서 __match_args__ 조회
이 PEP의 초기 버전에서는 패턴에 지정된 클래스가 아니라 대상의 클래스에서 __match_args__를 조회했습니다. 이는 몇 가지 이유로 거부되었습니다.:
* 패턴에 지정된 클래스를 사용하면 최적화에 더 적합하고 더 나은 성능을 제공할 수 있습니다.
* 패턴에 지정된 클래스를 사용하면 경우에 따라 더 나은 오류 보고를 제공할 수 있습니다.
* 두 접근 방식 모두 완벽하지 않으며, 양쪽 모두 특이한 경계 사례가 있습니다. 현 상태를 유지하면 중단을 최소화할 수 있습니다.
__match_class__와 __match_container__를 단일 값으로 결합하기
이 PEP의 이전 버전에서는 __match_class__와 __match_container__를 단일 값인 __match_kind__로 결합했습니다. 단일 값을 사용하면 성능 측면에서 약간의 이점이 있지만, 클래스 매칭 동작을 재정의할 때 컨테이너 매칭에 의도하지 않은 변경이 발생할 가능성이 높으며, 그 반대도 마찬가지입니다.
보류된 아이디어
이 PEP의 원래 버전에는 매칭 종류 MATCH_POSITIONAL과 특수 메서드 __deconstruct__가 포함되어 있었으며, 이를 통해 클래스가 매칭을 완전히 제어할 수 있었습니다. 이는 sympy와 같은 라이브러리에 중요합니다.
예를 들어, sympy를 사용하여 다음과 같이 작성하려 할 수 있습니다.:
# sin(x)**2 + cos(x)**2 == 1
case Add(Pow(sin(a), 2), Pow(cos(b), 2)) if a == b:
return 1
현재 패턴 매칭으로 sympy가 위치 패턴을 지원하도록 하는 것은 가능하지만 까다롭습니다. 이러한 추가 기능을 사용하면 쉽게 구현할 수 있습니다 [9].
이 아이디어는 3.11을 위한 향후 PEP에 포함될 예정입니다. 그러나 3.10 개발 주기에서는 이러한 변경을 하기에는 이미 너무 늦었습니다.
모든 클래스 매칭을 거부하기 위한 별도의 값
이 PEP의 이전 버전에는 __match_class__에 별도의 값이 있었으며, 이를 통해 클래스가 분해가 필요했을 모든 클래스 패턴과 매칭되지 않도록 할 수 있었습니다. 그러나 MATCH_POSITIONAL이 도입되면 이는 불필요해지고, 매우 드문 경우에 대한 명세를 복잡하게 만듭니다.
코드 예제
class Symbol:
__match_class__ = MATCH_SELF
이것은:
case [a, b] if a is b:
다음으로 변환됩니다.:
$kind = type($value).__match_container__
if $kind != MATCH_SEQUENCE:
FAIL
if len($value) != 2:
FAIL
a, b = $value
if not a is b:
FAIL
이것은:
case [a, *b, c]:
다음으로 변환됩니다.:
$kind = type($value).__match_container__
if $kind != MATCH_SEQUENCE:
FAIL
if len($value) < 2:
FAIL
a, *b, c = $value
이것은:
case {"x": x, "y": y} if x > 2:
다음으로 변환됩니다.:
$kind = type($value).__match_container__
if $kind != MATCH_MAPPING:
FAIL
$tmp = $value.get("x", $sentinel)
if $tmp is $sentinel:
FAIL
x = $tmp
$tmp = $value.get("y", $sentinel)
if $tmp is $sentinel:
FAIL
y = $tmp
if not x > 2:
FAIL
이것은:
case {"x": x, "y": y, **z}:
다음으로 변환됩니다.:
$kind = type($value).__match_container__
if $kind != MATCH_MAPPING:
FAIL
$tmp = dict($value)
if not $tmp.keys() >= {"x", "y"}:
FAIL
x = $tmp.pop("x")
y = $tmp.pop("y")
z = $tmp
이것은:
match ClsName(x, y):
다음으로 변환됩니다.:
if not isinstance($value, ClsName):
FAIL
$attrs = ClsName.__match_args__
if len($attr) < 2:
FAIL
try:
x = getattr($value, $attrs[0])
y = getattr($value, $attrs[1])
except AttributeError:
FAIL
이것은:
match ClsName(a=x, b=y):
다음으로 변환됩니다.:
if not isinstance($value, ClsName):
FAIL
try:
x = $value.a
y = $value.b
except AttributeError:
FAIL
이것은:
match ClsName(x, a=y):
다음으로 변환됩니다.:
if not isinstance($value, ClsName):
FAIL
$attrs = ClsName.__match_args__
if len($attr) < 1:
raise TypeError(...)
$positional_names = $attrs[:1]
try:
x = getattr($value, $attrs[0])
if "a" in $positional_names:
raise TypeError(...)
y = $value.a
except AttributeError:
FAIL
class Basic:
__match_class__ = MATCH_POSITIONAL
def __deconstruct__(self):
return self._args
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.