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를 적용하면 다음과 같이 됩니다.
- 패턴 매칭의 의미론이 더 명확해지므로 패턴을 추론하기가 더 쉬워집니다.
- 패턴 매칭을 더 효율적인 방식으로 구현할 수 있게 됩니다.
- 클래스가 어떤 패턴에 매치될지를 좀 더 제어할 수 있게 되므로, 복잡한 클래스에서도 패턴 매칭을 더 유용하게 사용할 수 있습니다.
동기
PEP 634에 기술된 Python의 패턴 매칭은 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” 매치를 지원해야 합니다. PEP 634에서도 sympy가 이 패턴을 지원할 수는 있지만 조금 까다롭습니다. 이 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
허용되는 최적화
위 의미 체계를 그대로 구현하면 중복 작업과 복사가 많이 발생합니다. 그러나 단순한 구현에 의미를 보존하는 변환을 적용하면 위 의미 체계를 효율적으로 구현할 수 있습니다.
매칭을 수행할 때 구현은 다음 함수와 메서드를 순수(pure)한 것으로 취급할 수 있습니다.
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
case 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.