Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

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

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 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 (패턴을 생략하면 __을 의미합니다)

이 접근 방식의 목적은 다음과 같습니다.

  • 이름 없이 사용하는 이름, 속성 조회 및 리터럴 값을 처리하기 위한 최적의 기본 옵션을 사전에 결정할 필요 없이 초기 형태의 패턴 매칭을 개발하고 출시할 수 있도록 합니다.
  • 추상 구문 트리 수준에서 패턴 매칭을 명시적으로 정의하여, 패턴 매칭의 의미론과 표면 구문에 대한 명세를 명확하게 분리할 수 있도록 합니다.
  • 동일한 객체에서 여러 속성을 포함하는 튜플을 더 쉽게 가져오는 방법으로 일반 표현식에 채택될 가능성이 있는, 명확하고 간결한 “덕 타이핑” 구문을 정의합니다.

이 제안은 PEP 634와 비교하여, as 키워드를 사용하지 않고 “오른쪽에 바인딩”하는 구문(PEP 634의 매핑 패턴과 클래스 패턴에서 캡처 패턴 사용)이나 동일한 패턴에서 왼쪽과 오른쪽 모두에 바인딩하는 구문(PEP 634의 캡처 패턴을 AS 패턴과 함께 사용)을 의도적으로 제거합니다.

다른 PEP와의 관계

이 PEP는 PEP 634에 의존하는 동시에 경쟁합니다. PEP 작성자는 match 문이 학습 과정에 추가하는 복잡성을 감수할 만큼 언어에 충분히 가치 있는 추가 기능이라는 점에는 동의하지만, match 패턴에서 이름 바인딩 연산과 값 조회 연산을 구분하는 구문으로서 “단순한 이름 대 리터럴 또는 속성 조회”가 실제로 충분한 구별을 제공한다는 생각에는 동의하지 않습니다(적어도 Python에서는 그렇습니다).

이 PEP는 선택된 와일드카드 패턴을 사용하여 이름 바인딩을 건너뛰는 기능을 match 패턴에서만이 아니라 모든 곳에서 지원해야 한다는 PEP 640의 취지에 동의하지만, 이제 와일드카드 구문에 다른 표기(?가 아닌 __)를 제안합니다. 따라서 이 문서의 형태로는 PEP 640과 경쟁하지만, __을 일반 식별자로 사용하는 것을 폐기하고 대신 항상 새로운 지역 변수 바인딩 생성을 건너뛰는 범용 와일드카드 표식으로 전환하자는 제안을 보완할 수 있습니다.

아직 PEP로 제출되지는 않았지만, Mark Shannon은 PEP 634의 패턴 매칭 제안에 대한 런타임 의미론상의 여러 우려를 표현한 사전 PEP 초안 [8]을 작성했습니다. 이 PEP는 주요 의미론 변경보다는 주로 표면 구문 변경을 다루지만, 표면 구문의 세부 사항과 코드 생성 단계의 의미론을 더 잘 분리할 수 있도록 추상 구문 트리 정의를 더욱 명시적으로 만들어야 한다고 제안한다는 점에서 해당 초안과 어느 정도 상호 보완적입니다. 해당 사전 PEP 초안에는 이 PEP가 명시적으로 거부하는 한 가지 구체적인 아이디어가 있습니다. 서로 다른 종류의 매칭이 상호 배타적이라는 생각입니다. 동일한 값이 서로 다른 종류의 구조적 패턴과 일치하는 것은 전적으로 가능하며, 어느 패턴이 우선할지는 match 문의 case 순서에 따라 의도적으로 결정됩니다.

동기

원래의 PEP 622(이후 PEP 634, PEP 635, PEP 636으로 분리됨)는 구문 설계에 명시되지 않았지만 필수적인 가정을 포함하고 있었습니다. 즉, 일반 표현식 기존 할당 대상 구문도 매치 패턴에 사용되는 구문을 위한 적절한 기반을 제공하지 않는다는 가정입니다.

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는 할당 대상 구문이 전혀 재사용되지 않도록 하여 기존 구문에 대한 경험을 바탕으로 새로운 구성 요소에 관해 잘못된 추론을 이끌어 낼 가능성을 줄이려 합니다.

마지막으로, 이 제안의 3번째 반복안(추론된 패턴을 완전히 제거한 버전)을 완성하기 전에 PEP 작성자는 PEP 20의 다음 항목을 상당히 오랫동안 되새겼습니다.

  • 명시적인 것이 암시적인 것보다 낫습니다.
  • 특수한 경우는 규칙을 깨뜨릴 만큼 특별하지 않습니다.
  • 모호한 상황에서는 추측하려는 유혹을 물리치십시오.

명시적인 구문으로 시작하면 나중에 언제든지 구문적 단축형을 추가할 수 있습니다(예를 들어, 원래의 더 장황한 형식을 수년간 사용한 경험을 쌓은 뒤에야 UnionOptional 타입 힌트에 대한 단축형을 추가하자는 최근 제안을 생각해 보십시오). 반면 처음부터 축약형만 사용한다면 향후 릴리스에서 그러한 결정을 재검토할 실질적인 방법이 없습니다.

사양

이 PEP는 PEP 634의 전반적인 match/case 문 구조와 의미 체계를 유지하지만, 패턴 매칭 컨텍스트에서 추론할 필요 없이 사용자 의도가 구체적인 구문에 명시되도록 하는 여러 가지 변경 사항을 제안합니다.

제안된 추상 구문 트리에서도 의미 체계는 항상 명시적이며, 추론이 필요하지 않습니다.

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_expressionblock규칙은 standard Python grammar의 일부입니다.

열린 패턴은 여러 토큰으로 구성되며 닫는 구분 기호로 반드시 끝나지는 않는 패턴입니다(예: __ as x, int() | bool()). 사람이 읽을 때 모호해지는 것을 방지하기 위해 열린 패턴의 사용은 최상위 패턴과 그룹 패턴(괄호로 둘러싸인 패턴)으로 제한됩니다.

닫힌 패턴은 단일 토큰으로 구성되거나(즉, __), 구문에 닫는 구분 기호가 필수적으로 포함되는 패턴입니다(예: [as x, as y], object{.x as x, .y as y}).

관련 PEP 634와 마찬가지로 matchcase 키워드는 소프트 키워드입니다. 즉, 예상되는 위치에 콜론이 없는 경우의 줄 시작을 포함하여 다른 문법적 맥락에서는 예약어가 아닙니다. 이는 match 문 또는 case 블록의 일부일 때만 해당 단어가 키워드로 인식되며, 그 밖의 모든 맥락에서는 변수 이름이나 인자 이름으로 사용할 수 있음을 의미합니다.

관련 PEP 634와 달리 패턴은 추상 구문 트리에서 새로운 종류의 노드로 명시적으로 정의됩니다. 표면 구문을 기존 표현식 노드와 공유하는 경우에도 파서는 별도의 추상 노드를 출력합니다.

참고로, 표면 구문에서 match_stmtcompound_statement의 새로운 대안이며, 추상 구문에서 Matchstmt의 새로운 대안입니다.

패턴 매칭 의미론

이 PEP는 PEP 634에서 제안된 전반적인 패턴 매칭 의미론을 대체로 유지합니다.

제안된 패턴 구문은 크게 변경되며, 아래에서 자세히 설명합니다.

또한 클래스에서 정의된 제약 조건(PEP 634의 클래스 패턴)의 의미론을 일부 변경하여 내장 타입을 특수하게 처리할 필요를 없애자는 제안도 있습니다. 대신 인스턴스 속성 제약 조건을 위한 전용 구문을 도입하여, 해당 내장 타입에 필요한 동작이 __match_args__None으로 설정된 모든 타입에 적용되도록 지정할 수 있습니다.

가드

이 PEP는 PEP 634에서 제안된 가드 절 의미론을 유지합니다.

그러나 가드 절이 있을 때 case 패턴이 닫힌 패턴이어야 하도록 구문이 약간 변경됩니다.

이렇게 하면 패턴이 끝나는 지점과 가드 절이 시작되는 지점이 독자에게 더 명확해집니다. (이는 주로 OR 패턴에서 발생할 수 있는 문제로, 가드 절이 최종 패턴에서 조건식의 시작처럼 보입니다. 실제로 그렇게 작성하는 것은 올바른 구문이 아니므로 컴파일러 관점에서는 모호하지 않지만, 사람 독자에게는 그 구분이 명확하지 않을 수 있습니다)

반박 불가능한 case 블록

캡처 패턴이 더 이상 AS 패턴과 별개의 개념으로 존재하지 않으므로, 이 PEP에서는 PEP 634에 비해 반박 불가능한 case 블록의 정의가 약간 변경됩니다.

이 점을 제외하면 반박 불가능한 case의 처리는 PEP 634와 동일합니다.

  • 와일드카드 패턴은 반박 불가능합니다.
  • 왼쪽이 반박 불가능한 AS 패턴
  • 하나 이상의 반박 불가능한 패턴을 포함하는 OR 패턴
  • 괄호로 묶인 반박 불가능한 패턴
  • 가드가 없고 패턴이 반박 불가능하면 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를 사용하여 매칭된 주제에서 추출한 요소를 지역 변수에 바인딩할 수 있습니다. 이러한 요소는 시퀀스 패턴의 전용 MatchRestOfSequence노드를 제외하면 대부분 추상 구문 트리에서 MatchAs노드로 표현됩니다.

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에서는 복소수 리터럴이 파서에 의해 받아들여지려면 괄호로 묶여 있어야 합니다. 이 점에 대한 논의는 Deferred Ideas 절을 참조하십시오.

이 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에서는 이중 밑줄을 선택합니다. 이중 밑줄은 향후 언어 전체에서 일관되게 만들 수 있는 더 명확한 경로가 있는 반면, "_"에 대해서는 국제화 관련 사용 사례로 인해 그 경로가 막혀 있기 때문입니다.

사용 예:

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에서 할당문 iterable 언패킹과 동일한 모든 구문적 유연성을 허용하는 반면, 이 PEP에서는 시퀀스 패턴을 대괄호 형식으로만 제한합니다. iterable 언패킹에서는 열린 형식과 괄호 형식이 대괄호보다 훨씬 더 널리 사용되므로, 이는 iterable 언패킹과 시퀀스 매칭이 동일한 연산이 아닙니다라는 점을 강조하는 데 도움이 됩니다. 또한 괄호 형식에서 단일 요소 시퀀스 패턴과 그룹 패턴 사이에 발생하는 모호성 문제도 피할 수 있습니다.

매핑 제약

표면 구문:

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에 바인딩됩니다(추가 키-값 쌍이 없으면 딕셔너리는 비어 있습니다).

매핑 패턴에는 이중 별표 패턴이 최대 하나만 포함될 수 있으며, 반드시 마지막에 와야 합니다.

값 하위 패턴은 대부분 닫힌 패턴이어야 하지만, 값 제약에서는 괄호를 생략할 수 있습니다(: 키/값 구분자는 해당 항목이 일반 비교 연산처럼 보이지 않도록 하기 위해 여전히 필요합니다).

매핑 값은 괄호나 : 키/값 구분자 없이 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}). 이 점에 대한 추가 논의는 Deferred Ideas 절을 참조하십시오.

클래스 정의 제약 조건

표면 구문:

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_NAMEATTR=TARGET_NAME보다 바인딩임이 훨씬 더 분명합니다.
  • KEY as TARGET_NAMEKEY: TARGET_NAME보다 바인딩임이 훨씬 더 분명합니다.
  • (as TARGET_NAME_1) as TARGET_NAME_2TARGET_NAME_1 as TARGET_NAME_2보다 두 번의 바인딩임이 훨씬 더 분명합니다.

추측하고 싶은 유혹을 억제하기

PEP 635는 다른 언어에서 패턴 매칭이 사용되는 방식을 살펴보고, 그 정보를 활용하여 Python에서 패턴 매칭이 사용될 방식에 대한 타당한 예측을 시도합니다:

  • 값을 로컬 이름으로 추출하려는 경우가 로컬 이름에 저장된 값과 매칭하려는 경우보다 아마도 더 일반적일 것입니다.
  • 동일성에 따른 비교를 원하기보다 동등성에 따른 비교를 원하는 경우가 아마도 더 일반적일 것입니다.
  • 사용자는 문서를 읽거나 다른 사람에게 설명을 듣지 않고는 스스로 알아내지 못하더라도, 일반 이름은 값을 바인딩하고 속성 참조는 값을 조회한다는 점을 최소한 기억할 수 있을 것입니다.

분명히 하자면, 이러한 예측은 실제로 타당하다고 생각합니다. 그러나 이 문제를 처음부터 추측할 필요도 없다고 생각합니다. 사용자가 접두사 표식(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 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에 따라 훈련된 미적 감각을 가진 많은 사람이 이 기호와 뒤따르는 표현 사이에 공백을 넣으려 할 것이므로, 사실상 한 문자가 아닌 세 문자 접두사가 되기 때문입니다.
  • 매핑 패턴에서 사용할 때는 : 키/값 구분자와 == 접두사 사이에 공백이 있어야 합니다. 그렇지 않으면 토큰화기가 이를 잘못 분할하여 :== 대신 :==를 얻게 됩니다.
  • OR 패턴에서 사용할 때는 | 패턴 구분자와 == 접두사 사이에 공백이 있어야 합니다. 그렇지 않으면 토큰화기가 이를 잘못 분할하여 |== 대신 |==를 얻게 됩니다.
  • 관련 PEP 634 스타일의 클래스 패턴에서 사용하는 경우 = 키워드 구분자와 == 접두사 사이에 공백이 있어야 합니다. 그렇지 않으면 토크나이저가 이를 잘못 분리하여 === 대신 ===를 얻게 됩니다.

완전히 새로운 기호를 도입하는 대신, Steven은 이 장황함 문제에 대한 해결책으로 구문적으로 모호하지 않은 경우 접두사 표시를 생략할 수 있는 기능을 유지하자고 제안했습니다.

접두사 표시를 생략하자는 아이디어는 제안의 두 번째 개정판에서 받아들여졌지만, 모호성 우려로 인해 세 번째 개정판에서 다시 삭제되었습니다. 대신 다음 사항이 적용됩니다.

  • 클래스 패턴의 경우, 다른 구문 변경을 통해 동등성 제약을 .ATTR == EXPR로, 동일성 제약을 .ATTR is EXPR로 작성할 수 있으며, 두 방식 모두 읽기가 매우 쉽습니다.
  • 매핑 패턴에서는 추가적인 구문상의 복잡함을 그대로 허용합니다(적어도 현재로서는).
  • OR 패턴에서는 추가적인 구문상의 복잡함을 그대로 허용합니다(적어도 현재로서는). 그러나 membership constraints는 OR 패턴을 동등성 제약과 결합해야 할 필요를 줄이는 향후 경로를 제공할 수 있습니다. 대신 대조할 값들을 집합, 리스트 또는 튜플로 수집하게 됩니다.

이러한 관점에서 보면, 패턴 매칭 구문의 일부로 ?를 사용하는 것에 반대하는 PEP 635의 주장은 이 제안에도 그대로 적용되므로 PEP가 그에 맞게 수정되었습니다.

와일드카드 패턴 표시로 __ 사용하기

PEP 635?solely와일드카드 패턴 표시로 도입하는 것이 나쁜 생각이라는 점을 설득력 있게 주장합니다. 값 제약의 구문을 ??is 대신 기존 비교 연산을 사용하도록 변경했으므로, 이 주장은 이 PEP에도 그대로 적용됩니다.

그러나 Thomas Wouters가 [6]에서 지적했듯이, PEP 634_를 선택한 것은 여전히 문제가 됩니다. 소프트웨어 국제화와 대화형 프롬프트에서 _를 사용하므로, 매치 패턴이 Python의 다른 모든 부분과 permanent차이를 갖게 될 가능성이 높고, 이를 범용 “바인딩 건너뛰기” 표시로 사용할 현실적인 경로가 없기 때문입니다.

__는 기존 Python 코드에서 _가 갖는 다양한 의미에 관한 Stack Overflow 답변 [7]에서 가져온, “이 값은 필요하지 않음”이라는 대체 표시입니다. 이 답변은 원래 이 PEP의 작성자가 게시했습니다.

이 PEP는 또한 __와 관련된 특수 처리를 파서로 제한하는 구현 기법을 채택할 것을 제안합니다. 즉, 와일드카드 표시에 특화된 새로운 AST 노드 유형(MatchAlways)을 정의하고, 이를 Name 노드로서 AST에 전달하지 않습니다.

파서 내부에서 __는 구문 트리의 어느 위치에 있는지에 따라 매치 패턴에서 일반 이름을 의미하거나 와일드카드 표시를 의미하지만, 컴파일러의 나머지 부분에서는 Name("__")이 여전히 일반 변수 이름인 반면 MatchAlways()는 매치 패턴에서 항상 와일드카드 표시입니다.

_와 달리 __에는 다른 사용 사례가 없으므로, Python 전체에서 __를 “이 이름 바인딩을 건너뜀”이라는 의미로 만들면 언어의 나머지 부분과 식별자 처리의 일관성을 회복할 현실적인 경로가 생깁니다.

  • 인터프리터 자체에서는 __라는 이름의 변수 로드를 사용 중단 처리합니다. 이렇게 하면 __에서 읽을 때 사용 중단 경고가 발생하지만, 처음에는 해당 변수에 쓰는 동작이 변경되지 않습니다. 모든 이름 로드의 속도가 느려지는 것을 방지하려면, 표준 이름 로드 opcode에서 런타임 검사를 수행하는 대신 컴파일러가 사용 중단된 이름에 대한 추가 코드를 내보내도록 하여 이를 처리할 수 있습니다.
  • 적절한 수의 릴리스가 지난 후에는 파서를 변경하여 __를 대입 대상에 사용하는 모든 경우에 새로운 SkippedBinding AST 노드를 내보내고, 그에 맞게 컴파일러의 나머지 부분을 갱신합니다.
  • __를 소프트 키워드가 아니라 진정한 하드 키워드로 만드는 방안을 검토합니다.

_에는 이 사용 중단 경로를 적용할 수 없습니다. 인터프리터가 명목상 “상관없음” 표시로 사용된 _를 다시 읽으려는 시도와, 국제화 텍스트 번역 함수 또는 대화형 프롬프트의 마지막 문장 결과로서 _를 정당하게 읽는 경우를 구별할 방법이 없기 때문입니다.

이중 밑줄로 시작하는 이름은 컴파일 시간 상수(예: __debug__), 특수 메서드 또는 클래스 속성 이름 변환 등 언어에서 사용할 목적으로 이미 예약되어 있으므로, 여기서 __를 사용하는 것은 기존 접근 방식과 일관됩니다.

추상 구문 트리에서 패턴을 명시적으로 표현하기

PEP 634는 매치 문을 추상 구문 트리에 어떻게 표현해야 하는지 명시적으로 논의하지 않고, 그 세부 사항을 구현 과정에서 정의하도록 남겨 둡니다.

그 결과 PEP 634의 참조 구현은 분명히 작동하고 이 PEP의 참조 구현의 기반이 되었지만, 중요한 설계 결함도 포함하고 있습니다. 패턴을 표현식과 구별되는 것으로 간주해야 한다는 PEP 635의 설명에도 불구하고, 참조 구현은 패턴을 AST에서 표현식 노드로 표현하기 때문입니다.

그 결과 AST는 전혀 추상적이지 않습니다. 완전히 다르게 컴파일되어야 하는 노드(표현식이 아니라 패턴이기 때문)가 동일한 방식으로 표현되며, 구현 언어의 타입 시스템(예를 들어 CPython의 C)은 어떤 하위 노드가 일반 표현식이고 어떤 하위 노드가 하위 패턴인지 추적하는 데 어떠한 도움도 제공할 수 없습니다.

이 접근 방식을 계속하는 대신 이 PEP에서는 새로운 명시적 “pattern” 노드를 AST에 정의했습니다. 이를 통해 패턴과 허용되는 하위 노드를 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에서 제안한 구문에 맞게 PEP 635의 매핑 패턴 예제를 조정한 것입니다.:

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에서 연결된 클래스 매칭 예제 를 조정해 보면, 순수하게 위치 기반인 클래스 매칭에서 주된 영향은 클래스 매칭 변경이 아니라 값 제약 조건과 이름 바인딩의 변경에서 비롯됨을 알 수 있습니다:

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과 같은 상수, 01과 같이 명백히 특수한 숫자, 또는 변수 이름만큼이나 내용이 설명적인 문자열을 생각해 보십시오. 이는 739452와 같은 불투명한 숫자에 대한 난해한 검사와 대조됩니다.)

필수 괄호 일부를 선택 사항으로 만들기

이 PEP는 잠재적인 모호성이 있는 경우 괄호를 요구하는 쪽으로 상당히 치우쳐 있습니다.

그러나 적어도 논쟁의 여지 없이 지나치게 엄격한 경우가 여러 가지 있으며, 대부분 명시적 패턴을 포함하는 AS 패턴과 관련됩니다.

닫힌 패턴이 필요한 모든 위치에서 중첩된 패턴 역시 닫힌 패턴이어야 하므로 AS 패턴은 이중 괄호로 시작하게 될 수 있습니다: ((OPEN PTRN) as NAME)

하위 패턴이 닫혀 있어야 한다는 요구 사항 때문에, 이러한 많은 경우(예: 시퀀스 패턴의 하위 패턴)에는 CLOSED_PTRN as NAME을 직접 허용하는 것이 합리적일 것입니다.

필요한 괄호를 선택 사항으로 만드는 것은 하위 호환성을 유지하는 변경이므로 이후 제한을 완화하는 방안을 사례별로 검토할 수 있다는 점에서, 이 문제에 대한 추가 고려는 보류되었습니다.

복소수 리터럴을 닫힌 표현식으로 허용하기

PEP 634의 참조 구현에는 리터럴 값에 대한 임의의 이진 수치 연산은 허용하지 않으면서 복소수 리터럴을 허용하기 위해 파서와 나머지 컴파일러 모두에서 이진 연산을 특수 처리하는 코드가 많이 포함되어 있습니다.

이상적으로는 이 문제를 파서 계층에서 처리하여, 파서가 복소수가 미리 채워진 Constant AST 노드를 직접 출력하도록 해야 합니다. 그런 방식으로 동작한다면 복소수 리터럴은 다른 리터럴과 유사한 메커니즘을 통해 허용할 수 있습니다.

그러나 복소수 리터럴은 실제로 이런 방식으로 처리되지 않습니다. 대신 일반적인 BinOp 노드로 AST에 전달된 다음, AST에 대한 상수 폴딩 단계에서 복소수 값을 지닌 Constant 노드로 해석됩니다.

파서가 복소수 리터럴을 직접 해석하려면, 컴파일러가 토크나이저에 허수에 대한 별도의 토큰 형식(예: INUMBER)을 생성하도록 지시할 수 있어야 하며, 그러면 파서는 다른 이진 연산과 별도로 NUMBER + INUMBERNUMBER - INUMBER을 처리할 수 있습니다.

또는 새로운 ComplexNumber AST 노드 형식을 정의하여, 특정 노드가 임의의 이진 연산이 아니라 구체적으로 복소수 리터럴이어야 한다는 사실을 파서가 이후 컴파일러 단계에 알릴 수 있도록 할 수도 있습니다. 그러면 파서는 해당 노드에 대해 NUMBER + NUMBERNUMBER - NUMBER을 허용하고, ComplexNumber에 대한 AST 검증에서 리터럴의 실수부와 허수부가 예상대로 실수와 허수인지 확인하도록 맡길 수 있습니다.

현재 이 PEP는 이 문제의 처리를 보류하고, 대신 복소수 리터럴을 값 제약 조건과 매핑 패턴 키로 사용하려면 괄호로 묶도록 요구합니다.

매칭 패턴에서 부정된 제약 조건 허용하기

이 PEP에서 제안하는 구문에서는 != expr 또는 is not expr을 매칭 패턴으로 작성할 수 없습니다.

이 두 형식은 각각 부정된 동등성 제약 조건(즉, x != expr)과 부정된 동일성 제약 조건(즉, x is not expr)으로 해석될 가능성이 분명합니다.

그러나 두 형식 중 어느 하나라도 전용 구문을 정당화할 만큼 자주 사용될지는 전혀 명확하지 않으므로, 이 확장은 매칭 문에 대한 커뮤니티의 추가 경험이 쌓일 때까지 보류되었습니다.

매칭 패턴에서 멤버십 검사 허용하기

동등성 및 동일성 제약 조건에 사용되는 구문은 멤버십 검사로 간단히 확장할 수 있습니다: 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 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 문을 언어에 가치 있는 추가 기능으로 만드는 데 필수적인 것은 아니므로 이를 여기에 포함하기보다는 별도의 제안으로 미루는 것이 더 적절해 보입니다.

인스턴스 속성 제약의 기본 타입 추론

인스턴스 속성 제약을 위한 전용 구문을 사용하면 구문상의 모호성을 도입하지 않고 objectobject{.ATTR}에서 생략하여 {.ATTR}로 만들 수 있습니다(클래스가 지정되지 않은 경우 클래스 정의의 베이스 클래스 목록에서와 마찬가지로 object가 암시됩니다).

그러나 문자 6개를 절약하는 것이 매핑 패턴과 인스턴스 속성 패턴을 시각적으로 구별하기 어렵게 만들 만큼 가치가 있는지는 전혀 분명하지 않으므로, 이를 허용하는 문제는 향후 검토할 수 있는 주제로 미루었습니다.

시퀀스 패턴의 특수 사례 방지

현재 이 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]PEP 634 [4]에 대한 Brandt Bucher의 참조 구현에서 파생되었습니다.

이 PEP의 내용과 비교하면, 초안 참조 구현은 아직 MATCH_CLASS에서 여러 내장 및 표준 라이브러리 형식을 특수 처리하는 방식에 __match_args__None으로 설정되었는지를 확인하는 더 일반적인 검사를 추가하지 않았습니다. 클래스 정의 패턴은 현재도 __match_args__를 정의하지 않는 클래스를 허용합니다.

수정된 다른 모든 패턴은 PEP 634 대신 이 PEP를 따르도록 업데이트되었습니다.

match 패턴의 역구문 분석은 아직 업데이트된 v3 AST로 마이그레이션되지 않았습니다.

match 패턴용 AST 검사기는 아직 구현되지 않았습니다.

AST 검사기 전반에 대해서는 표현식 노드가 필요한 위치에 표현식 노드만 전달되고 있는지 확인하고 있는지 아직 검토되지 않았습니다.

이 PEP의 예제는 아직 테스트 사례로 변환되지 않았으므로 오타 및 기타 오류가 포함되어 있을 가능성이 있습니다.

기존 PEP 634 테스트 중 일부는 아직 새로운 SyntaxError 테스트로 변환되지 않았습니다.

문서는 아직 업데이트되지 않았습니다.

감사의 말

이 PEP의 제안은 이미 잘 구성된 아이디어의 가독성을 개선하려는 시도에 불과하므로, PEP 622PEP 634/PEP 635/PEP 636 저자들에게 감사를 표합니다. 이 제안은 오직 단축형만 정의하려 하기보다 더 명시적인 구문으로 시작한 뒤 특히 흔한 연산을 위한 구문적 단축 표현을 나중에 도입할 가능성을 열어 두는 편이 더 나은 선택이라고 제안합니다. 두 PEP의 내용이 동일하거나 적어도 매우 유사한 사양 영역에서는, 의도된 동작을 설명하는 이 PEP의 내용이 PEP 634의 내용에서 직접 파생된 경우가 많습니다.

Steven D’Aprano께 감사드립니다. 그는 이 PEP의 핵심 목표를 기존 비교 토큰을 사용하여 달성할 수 있다는 설득력 있는 주장을 제시했습니다. 즉, “대부분의 사용자가 대부분의 경우 가장 원할 것”에 대한 우리의 추측이 적어도 일부 사용자에게는 때때로 필연적으로 틀릴 때 컴파일러를 재정의할 수 있는 능력을 제공하고, PEP 634의 구문적 설탕 일부를 유지하여(의미론적 정의는 약간 다릅니다) 대부분의 상황에서 PEP 634와 동일한 간결성을 얻을 수 있다는 주장입니다. (Paul Sokolosvsky도 독립적으로 ==? 대신 동등성 제약 조건에 대한 더 쉽게 이해할 수 있는 접두사로 사용하자고 제안했습니다.)

관련 PEP 640을 발표하고 구조적 패턴 매칭 제안을 공개적으로 검토하여, 이 PEP의 저자가 여기서 제안하는 매치 패턴 전용 소프트 키워드로 무기한 남겨 두는 대신 향후 PEP에서 단순 이름이 예상되는 모든 위치에서 참조 바인딩을 항상 건너뛰는 하드 키워드로 만들 수 있는 와일드카드 패턴 구문을 계속 옹호하도록 설득한 Thomas Wouters에게 감사를 표합니다.

클래스 패턴과 매핑 패턴 내부의 하위 요소 캡처를 위해 제안된 구문, 특히 “오른쪽으로 캡처하기”의 문제를 PEP 저자가 더욱 면밀히 살펴보도록 이끌어 주신 Joao Bueno와 Jim Jewett께 감사드립니다. 이 검토로 인해 제안의 v2와 v3 사이에 중대한 변경이 이루어졌습니다.

참고 문헌

부록 A – 전체 문법

다음은 PEP 634를 대체하는 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: 추상 구문 트리 변경 사항 요약

이 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 634에 비해 이 PEP에서는 다음과 같은 주요 사항이 변경됩니다:

  • 패턴에 expr형식을 재사용하는 대신 AST에 새로운 pattern형식을 정의합니다
  • 새로운 MatchAsMatchOr AST 노드를 expr형식에서 pattern형식으로 이동합니다
  • 와일드카드 패턴이 _ (밑줄 하나)에서 __ (밑줄 두 개)로 변경되고, AST에 전용 MatchAlways노드가 추가됩니다.
  • 의도의 모호성으로 인해 값 패턴과 리터럴 패턴이 제거됩니다.
  • 새로운 표현식 범주인 “닫힌 표현식”이 도입됩니다.
  • 닫힌 표현식은 기본 표현식이거나, 우선순위가 높은 단항 연산자(+, -, ~) 중 하나가 앞에 붙은 닫힌 표현식입니다.
  • 새로운 패턴 유형인 “값 제약 패턴”이 도입됩니다.
  • 값 제약은 Constant (리터럴), UnaryOp (음수), BinOp (복소수), Attribute (속성 조회)의 조합을 허용하는 대신 전용 MatchValueAST 노드를 사용합니다.
  • 값 제약 패턴은 같음 제약 또는 동일성 제약입니다.
  • 같음 제약은 그 밖에는 임의의 닫힌 표현식 앞에 ==를 접두사 표식으로 사용합니다: == EXPR
  • 동일성 제약은 그 밖에는 임의의 닫힌 표현식 앞에 is를 접두사 표식으로 사용합니다: is EXPR
  • 의도의 모호성으로 인해 캡처 패턴이 제거됩니다. 모든 캡처 연산은 as키워드를 사용하며(시퀀스 매칭에서도 마찬가지임), AST에서는 MatchAs 또는 MatchRestOfSequence노드로 표현됩니다.
  • AS 패턴의 장황함을 줄이기 위해 as NAME을 사용할 수 있으며, __ as NAME과 같은 의미입니다.
  • 시퀀스 패턴은 할당 대상과 동일한 구문적 유연성을 제공하는 대신 대괄호를 사용하도록 요구하도록 변경됩니다(할당문에서는 괄호나 대괄호로 둘러싸거나 둘러싸지 않은 튜플로 구분된 대상을 사용하여 이터러블 언패킹을 나타낼 수 있습니다).
  • 시퀀스 패턴은 List를 재사용하는 대신 전용 MatchSequenceAST 노드를 갖습니다.
  • 매핑 패턴은 키로 임의의 닫힌 표현식을 허용하도록 변경됩니다.
  • 매핑 패턴은 Dict를 재사용하는 대신 전용 MatchMappingAST 노드를 갖습니다.
  • 매핑 패턴의 장황함을 줄이기 위해 KEY : __ as NAMEKEY 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를 사용할 것을 제안했습니다.

두 번째 발표 버전은 여러 상황에서 PEP 634스타일의 추론된 제약을 유지하면서도 명시적 같음 제약에 == EXPR, 명시적 동일성 제약에 is EXPR를 사용할 수 있도록 한 Steven D’Aprano의 반대 제안을 대체로 채택했습니다.

세 번째로 발표된(그리고 현재의) 개정안은 추론된 패턴을 완전히 제거했습니다. case {key: NAME}:case cls(attr=NAME):패턴이 as 키워드를 사용하지 않고도 다른 서브 표현식의 오른쪽에 나타난 NAME을 모두 바인딩한다는 우려를 해소하려는 시도였습니다. 개정된 제안은 case TARGET1 as TARGET2:를 작성할 가능성도 제거했으며, 이는 주어진 두 이름 모두에 바인딩하게 됩니다. 이러한 변경 사항 중 가장 우려스러웠던 것은 case cls(attr=TARGET_NAME):였습니다. 바인딩 대상이 오른쪽에 있는 상태에서 =를 사용하는 방식이 대입문, 함수 호출 및 함수 시그니처 선언에서 일어나는 방식과 정확히 반대였기 때문입니다.