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

Python Enhancement Proposals

PEP 642 – Explizite Muster-Syntax für strukturelles Pattern Matching

Autor:
Alyssa Coghlan <ncoghlan at gmail.com>
BDFL-Delegate:

Discussions-To:
Python-Dev Liste
Status:
Abgelehnt
Typ:
Standards Track
Benötigt:
634
Erstellt:
26-Sep-2020
Python-Version:
3.10
Post-History:
31-Oct-2020, 08-Nov-2020, 03-Jan-2021
Resolution:
Python-Dev Nachricht

Inhaltsverzeichnis

Zusammenfassung

Dieses PEP behandelt einen alternativen Syntaxvorschlag für das strukturelle Pattern Matching von PEP 634, der explizite Präfixe für alle Capture-Muster und Wertbeschränkungen erfordert. Es schlägt außerdem eine neue dedizierte Syntax für Instanzattributmuster vor, die stärker auf die vorgeschlagene Mapping-Muster-Syntax abgestimmt ist.

Während das Ergebnis zwangsläufig ausführlicher ist als die vorgeschlagene Syntax in PEP 634, ist es immer noch deutlich weniger ausführlich als der aktuelle Stand.

Als Beispiel würde die folgende Match-Anweisung "host"- und "port"-Details aus einer 2-Element-Sequenz, einem Mapping mit "host"- und "port"-Schlüsseln, einem beliebigen Objekt mit "host"- und "port"-Attributen oder einem "host:port"-String extrahieren und den "port" in den letzteren drei Fällen als optional behandeln

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)

Auf hoher Ebene schlägt dieses PEP vor, die verschiedenen verfügbaren Mustertypen wie folgt zu kategorisieren:

  • Wildcard-Muster: __
  • Gruppenmuster: (PTRN)
  • Wertbeschränkungsmuster
    • Gleichheitsbeschränkungen: == EXPR
    • Identitätsbeschränkungen: is EXPR
  • Strukturelle Einschränkungsmuster
    • Sequenzbeschränkungsmuster: [PTRN, as NAME, PTRN as NAME]
    • Mapping-Beschränkungsmuster: {EXPR: PTRN, EXPR as NAME}
    • Instanzattributbeschränkungsmuster: CLS{.NAME, .NAME: PTRN, .NAME == EXPR, .NAME as NAME}
    • Klassendefinierte Beschränkungsmuster: CLS(PTRN, PTRN, **{.NAME, .NAME: PTRN, .NAME == EXPR, .NAME as NAME})
  • ODER-Muster: PTRN | PTRN | PTRN
  • AS-Muster: PTRN as NAME (Weglassen des Musters impliziert __)

Die Absicht dieses Ansatzes ist es,

  • eine erste Form des Pattern Matching zu entwickeln und zu veröffentlichen, ohne dass im Voraus über die besten Standardoptionen für die Behandlung von bloßen Namen, Attribut-Lookups und literalen Werten entschieden werden muss.
  • sicherzustellen, dass Pattern Matching explizit auf der Ebene des Abstract Syntax Tree definiert wird, was eine klare Trennung der Spezifikationen der Semantik und der Oberflächensyntax für Pattern Matching ermöglicht.
  • eine klare und prägnante "Duck-Typing"-Syntax zu definieren, die potenziell in gewöhnlichen Ausdrücken als Mittel zur einfacheren Gewinnung eines Tupels mit mehreren Attributen desselben Objekts übernommen werden könnte.

Im Vergleich zu PEP 634 eliminiert der Vorschlag auch bewusst jede Syntax, die "nach rechts bindet" ohne das Schlüsselwort as zu verwenden (unter Verwendung von Capture-Mustern in den Mapping-Mustern und Klassmustern von PEP 634) oder die sowohl nach links als auch nach rechts im selben Muster bindet (unter Verwendung der Capture-Muster von PEP 634 mit AS-Mustern).

Beziehung zu anderen PEPs

Dieses PEP hängt von PEP 634 ab und konkurriert gleichzeitig mit ihm – der Autor des PEP stimmt zu, dass Match-Anweisungen eine ausreichend wertvolle Ergänzung für die Sprache wären, die die zusätzliche Komplexität, die sie für den Lernprozess mit sich bringen, wert ist, stimmt aber der Idee nicht zu, dass "einfacher Name vs. Literal oder Attribut-Lookup" wirklich eine angemessene syntaktische Unterscheidung zwischen Namensbindung und Wert-Lookup-Operationen in Match-Mustern bietet (zumindest für Python).

Dieses PEP stimmt im Geiste mit PEP 640 überein (dass das gewählte Wildcard-Muster zum Überspringen einer Namensbindung überall unterstützt werden sollte, nicht nur in Match-Mustern), schlägt aber nun eine andere Schreibweise für die Wildcard-Syntax vor (__ anstelle von ?). Als solches konkurriert es mit PEP 640 in seiner jetzigen Form, würde aber einen Vorschlag zur Deprekation der Verwendung von __ als gewöhnlichen Bezeichner ergänzen und ihn stattdessen zu einem allgemeinen Wildcard-Marker machen, der immer die Erstellung einer neuen lokalen Variablenspeicherung überspringt.

Obwohl es noch nicht als PEP vorgelegt wurde, hat Mark Shannon einen Pre-PEP-Entwurf [8], der mehrere Bedenken hinsichtlich der Laufzeitsemantik des Pattern Matching-Vorschlags in PEP 634 äußert. Dieses PEP ist in gewisser Weise komplementär zu diesem, da es, obwohl es sich hauptsächlich um Änderungen der Oberflächensyntax und nicht um größere semantische Änderungen handelt, vorschlägt, die Definition des Abstract Syntax Tree expliziter zu gestalten, um die Details der Oberflächensyntax besser von der Semantik des Code-Generierungsschritts zu trennen. Eine spezifische Idee in diesem Pre-PEP-Entwurf wird von diesem PEP ausdrücklich abgelehnt: die Idee, dass die verschiedenen Arten des Matchings sich gegenseitig ausschließen. Es ist durchaus möglich, dass derselbe Wert verschiedenen Arten von strukturellen Mustern entspricht, und welche davon Vorrang hat, wird absichtlich durch die Reihenfolge der Fälle in der Match-Anweisung gesteuert.

Motivation

Das ursprüngliche PEP 622 (das später in PEP 634, PEP 635 und PEP 636 aufgeteilt wurde) enthielt eine unausgesprochene, aber wesentliche Annahme in seinem Syntaxdesign: dass weder gewöhnliche Ausdrücke noch die vorhandene Zuweisungsziel-Syntax eine ausreichende Grundlage für die in Match-Mustern verwendete Syntax bieten.

Obwohl das PEP diese Annahme nicht explizit darlegte, erklärte einer der Autoren des PEP dies deutlich auf python-dev [1]

Das eigentliche Problem, das ich sehe, ist, dass hier unterschiedliche Kulturen/Intuitionen grundlegend aufeinanderprallen. Insbesondere begrüßen viele Programmierer Pattern Matching als eine "erweiterte Switch-Anweisung" und finden es daher seltsam, dass Namen gebunden und nicht als Ausdrücke für Vergleiche verwendet werden. Andere argumentieren, dass es im Widerspruch zu aktuellen Zuweisungsanweisungen steht und hinterfragen, warum Punktnamen _/nicht/_ bindend sind. Was alle Gruppen zu haben scheinen, ist, dass sie sich auf _/ihr/_ Verständnis und ihre Interpretation der neuen Match-Anweisung als "konsistent" oder "intuitiv" beziehen – und natürlich darauf hinweisen, wo wir als PEP-Autoren bei unserem Design falsch lagen.

Aber hier ist der Haken: Zumindest in der Python-Welt ist Pattern Matching, wie es von diesem PEP vorgeschlagen wird, eine beispiellose und neue Herangehensweise an ein gängiges Problem. Es ist nicht einfach eine Erweiterung von etwas, das bereits vorhanden ist. Noch schlimmer: Bei der Gestaltung des PEP stellten wir fest, dass man, egal aus welchem Blickwinkel man es betrachtet, auf Probleme scheinbarer "Inkonsistenzen" stößt (was bedeutet, dass Pattern Matching nicht sinnvoll auf eine "lineare" Erweiterung bestehender Features reduziert werden kann): Es gibt immer etwas, das grundlegend über das hinausgeht, was in Python bereits vorhanden ist. Deshalb argumentiere ich, dass Argumente, die auf dem basieren, was "intuitiv" oder "konsistent" ist, _/in diesem Fall/_ keinen Sinn ergeben.

Die erste Iteration dieses PEP entstand dann aus dem Versuch zu zeigen, dass die zweite Behauptung nicht zutreffend war und dass Match-Muster als eine Variation von Zuweisungszielen behandelt werden könnten, ohne zu inhärenten Widersprüchen zu führen. (Ein früherer PR, der eingereicht wurde, um diese Option in den Abschnitt "Abgelehnte Ideen" des ursprünglichen PEP 622 aufzunehmen, wurde zuvor abgelehnt [2]).

Der Überprüfungsprozess für dieses PEP legte jedoch stark nahe, dass nicht nur die von Tobias in seiner E-Mail erwähnten Widersprüche bestanden, sondern dass sie auch besorgniserregend genug waren, um Zweifel an dem in PEP 634 vorgestellten Syntaxvorschlag zu säen. Folglich wurde dieses PEP so geändert, dass es noch weiter geht als PEP 634 und die Übereinstimmung zwischen der Sequenzmuster-Syntax und der vorhandenen iterierbaren Entpackungs-Syntax weitgehend aufgibt (wobei die erste Frage des DLS'20-Papers [9] im Wesentlichen mit "Nicht wirklich, zumindest was die genaue Syntax betrifft" beantwortet wird: "Können wir ein Feature wie iterierbares Entpacken erweitern, um für allgemeinere Objekte und Datenlayouts zu funktionieren?").

Dies führte zu einer vollständigen Umkehrung der Ziele des PEP: Anstatt zu versuchen, die Ähnlichkeiten zwischen Zuweisung und Pattern Matching hervorzuheben, versucht das PEP nun sicherzustellen, dass die Zuweisungsziel-Syntax überhaupt nicht wiederverwendet wird, was die Wahrscheinlichkeit falscher Schlussfolgerungen über das neue Konstrukt basierend auf Erfahrungen mit bestehenden reduziert.

Bevor schließlich die 3. Iteration des Vorschlags abgeschlossen wurde (die abgeleitete Muster vollständig fallen ließ), dachte der Autor des PEP eine ganze Weile über die folgenden Einträge in PEP 20 nach:

  • Explizit ist besser als implizit.
  • Sonderfälle sind nicht besonders genug, um die Regeln zu brechen.
  • Bei Mehrdeutigkeit, widerstehe der Versuchung zu raten.

Wenn wir mit einer expliziten Syntax beginnen, können wir später immer noch syntaktische Abkürzungen hinzufügen (z. B. betrachten Sie die jüngsten Vorschläge, Abkürzungen für Union und Optional Typ-Annotationen hinzuzufügen, nur nach Jahren der Erfahrung mit den ursprünglichen, ausführlicheren Formen), während wir, wenn wir nur mit den abgekürzten Formen beginnen, keine wirkliche Möglichkeit haben, diese Entscheidungen in einer zukünftigen Version erneut zu überprüfen.

Spezifikation

Dieses PEP behält die Gesamtstruktur und Semantik der match/case Anweisungsstruktur und Semantik aus PEP 634 bei, schlägt aber mehrere Änderungen vor, die dazu führen, dass die Absicht des Benutzers explizit in der konkreten Syntax angegeben wird, anstatt aus dem Pattern Matching-Kontext abgeleitet werden zu müssen.

Im vorgeschlagenen Abstract Syntax Tree ist die Semantik ebenfalls immer explizit, ohne dass eine Ableitung erforderlich ist.

Die Match-Anweisung

Oberflächensyntax

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

Abstrakte Syntax

Match(expr subject, match_case* cases)
match_case = (pattern pattern, expr? guard, stmt* body)

Die Regeln star_named_expression, star_named_expressions, named_expression und block sind Teil der Standard-Python-Grammatik.

Offene Muster sind Muster, die aus mehreren Token bestehen und nicht unbedingt durch einen schließenden Begrenzer beendet werden (z. B. __ as x, int() | bool()). Um Mehrdeutigkeit für menschliche Leser zu vermeiden, ist ihre Verwendung auf Top-Level-Muster und Gruppenmuster (Muster, die von Klammern umschlossen sind) beschränkt.

Geschlossene Muster sind Muster, die entweder aus einem einzigen Token bestehen (d. h. __) oder einen schließenden Begrenzer als obligatorischen Bestandteil ihrer Syntax haben (z. B. [as x, as y], object{.x as x, .y as y}).

Wie in PEP 634 sind die Schlüsselwörter match und case Soft-Keywords, d. h. sie sind in anderen grammatikalischen Kontexten keine reservierten Wörter (einschließlich am Zeilenanfang, wenn dort kein Doppelpunkt erwartet wird). Das bedeutet, dass sie nur als Schlüsselwörter in einer Match-Anweisung oder einem Case-Block erkannt werden und in allen anderen Kontexten als Variablennamen oder Argumentnamen verwendet werden dürfen.

Im Gegensatz zu PEP 634 sind Muster explizit als eine neue Art von Knoten im Abstrakten Syntaxbaum definiert – auch wenn die Oberflächensyntax mit vorhandenen Ausdrucksknoten geteilt wird, gibt der Parser einen eindeutigen abstrakten Knoten aus.

Zur Einordnung ist match_stmt eine neue Alternative für compound_statement in der Oberflächensyntax und Match eine neue Alternative für stmt in der abstrakten Syntax.

Match-Semantik

Dieses PEP behält größtenteils die Gesamtsemantik des strukturellen Pattern Matching bei, die in PEP 634 vorgeschlagen wurde.

Die vorgeschlagene Syntax für Muster ändert sich erheblich und wird im Folgenden detailliert besprochen.

Es gibt auch einige vorgeschlagene Änderungen an der Semantik von klassendefinierten Beschränkungen (Klassmuster in PEP 634), um die Notwendigkeit zu eliminieren, spezielle Fälle für eingebaute Typen zu behandeln (stattdessen ermöglicht die Einführung einer dedizierten Syntax für Instanzattributbeschränkungen, dass das für diese eingebauten Typen benötigte Verhalten auf jeden Typ angewendet werden kann, der __match_args__ auf None setzt).

Guards

Dieses PEP behält die Guard-Klausel-Semantik bei, die in PEP 634 vorgeschlagen wurde.

Die Syntax wird jedoch geringfügig geändert, um zu verlangen, dass, wenn eine Guard-Klausel vorhanden ist, das Case-Muster ein geschlossenes Muster sein muss.

Dies macht für den Leser deutlicher, wo das Muster endet und die Guard-Klausel beginnt. (Dies ist hauptsächlich ein potenzielles Problem mit OR-Mustern, bei denen die Guard-Klausel wie der Beginn eines bedingten Ausdrucks im letzten Muster aussieht. Das tatsächlich zu tun, ist keine gültige Syntax, sodass es für den Compiler keine Mehrdeutigkeit gibt, aber der Unterschied ist für einen menschlichen Leser möglicherweise nicht so klar).

Irrefutable Case-Blöcke

Die Definition von irrefutable case blocks ändert sich in diesem PEP geringfügig im Vergleich zu PEP 634, da Capture-Muster nicht mehr als ein separates Konzept von AS-Mustern existieren.

Abgesehen von dieser Einschränkung ist die Behandlung von irrefutable cases dieselbe wie in PEP 634.

  • Wildcard-Muster sind irrefutable.
  • AS-Muster, deren linke Seite irrefutable ist.
  • ODER-Muster, die mindestens ein irrefutable Muster enthalten.
  • Geklammerte irrefutable Muster.
  • Ein Case-Block gilt als irrefutable, wenn er kein Guard hat und sein Muster irrefutable ist.
  • Eine Match-Anweisung darf höchstens einen irrefutable Case-Block haben und dieser muss der letzte sein.

Muster

Die Top-Level-Oberflächensyntax für Muster ist wie folgt:

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

Wie oben beschrieben, ist die Verwendung von offenen Mustern auf Top-Level-Case-Klauseln und beim Klammern in einem Gruppenmuster beschränkt.

Die abstrakte Syntax für Muster gibt explizit an, welche Elemente Unter-Muster und welche Elemente Unter-Ausdrücke oder Bezeichner sind.

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-Muster

Oberflächensyntax

as_pattern: [closed_pattern] pattern_as_clause
pattern_as_clause: 'as' pattern_capture_target
pattern_capture_target: !"__" NAME !('.' | '(' | '=')

(Hinweis: Der Name auf der rechten Seite darf nicht __ sein.)

Abstrakte Syntax

MatchAs(pattern? pattern, identifier target)

Ein AS-Muster gleicht das geschlossene Muster links vom Schlüsselwort as gegen das Subjekt ab. Wenn dies fehlschlägt, schlägt das AS-Muster fehl. Andernfalls bindet das AS-Muster das Subjekt an den Namen rechts vom Schlüsselwort as und ist erfolgreich.

Wenn kein abzugleichendes Muster angegeben ist, wird implizit das Wildcard-Muster (__) verwendet.

Um Verwechslungen mit dem Wildcard-Muster zu vermeiden, ist der doppelte Unterstrich (__) nicht als Capture-Ziel erlaubt (dies wird durch !"__" ausgedrückt).

Ein Capture-Muster ist immer erfolgreich. Es bindet den Subjektwert an den Namen gemäß den Regeln für Namensbindung, die für benannte Ausdrücke in PEP 572 festgelegt wurden. (Zusammenfassung: Der Name wird zu einer lokalen Variablen im nächstgelegenen umschließenden Funktionsbereich, es sei denn, es gibt eine zutreffende nonlocal- oder global-Anweisung.)

In einem gegebenen Muster darf ein gegebener Name nur einmal gebunden werden. Dies verbietet z. B. case [as x, as x]: ... erlaubt aber case [as x] | (as x).

Als offenes Muster ist die Verwendung von AS-Mustern auf Top-Level-Case-Klauseln und beim Klammern in einem Gruppenmuster beschränkt. Mehrere strukturelle Einschränkungen erlauben jedoch die Verwendung von pattern_as_clause an relevanten Stellen, um extrahierte Elemente des abgeglichenen Subjekts an lokale Variablen zu binden. Diese werden meist als MatchAs-Knoten im Abstract Syntax Tree dargestellt, abgesehen vom dedizierten MatchRestOfSequence-Knoten in Sequenzmustern.

ODER-Muster

Oberflächensyntax

or_pattern: '|'.simple_pattern+

simple_pattern: # Subnode where "as" and "or" patterns must be parenthesised
    | closed_pattern
    | value_constraint

Abstrakte Syntax

MatchOr(pattern* patterns)

Wenn zwei oder mehr Muster durch vertikale Balken (|) getrennt sind, nennt man dies ein ODER-Muster. (Ein einzelnes einfaches Muster ist einfach das).

Nur das letzte Unter-Muster darf irrefutable sein.

Jedes Unter-Muster muss dieselbe Menge an Namen binden.

Ein ODER-Muster gleicht nacheinander jedes seiner Unter-Muster gegen das Subjekt ab, bis eines erfolgreich ist. Das ODER-Muster gilt dann als erfolgreich. Wenn keines der Unter-Muster erfolgreich ist, schlägt das ODER-Muster fehl.

Unter-Muster müssen meistens geschlossene Muster sein, aber Klammern können für Wertbeschränkungen weggelassen werden.

Wertbeschränkungen

Oberflächensyntax

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

Abstrakte Syntax

MatchValue(matchop op, expr value)
matchop = EqCheck | IdCheck

Die Regel primary ist in der Standard-Python-Grammatik definiert und erlaubt nur Ausdrücke, die entweder aus einem einzigen Token bestehen oder deren Ende ein schließender Begrenzer sein muss.

Wertbeschränkungen ersetzen die PEP 634's Literal-Muster und Wert-Muster.

Gleichheitsbeschränkungen werden als == EXPR geschrieben, während Identitätsbeschränkungen als is EXPR geschrieben werden.

Eine Gleichheitsbeschränkung ist erfolgreich, wenn der Subjektwert mit dem rechts angegebenen Wert gleich verglichen wird, während eine Identitätsbeschränkung nur erfolgreich ist, wenn es sich um dasselbe Objekt handelt.

Die gegen die verglichenen Ausdrücke sind weitgehend auf entweder einzelne Token beschränkt (z. B. Namen, Zeichenketten, Zahlen, eingebaute Konstanten) oder auf Ausdrücke, deren Ende ein schließender Begrenzer sein muss.

Die Verwendung von binären Operatoren mit hoher Priorität ist ebenfalls zulässig, da das Risiko einer wahrgenommenen Mehrdeutigkeit gering ist und die Möglichkeit, negative Zahlen ohne Klammern anzugeben, wünschenswert ist.

Wenn derselbe Beschränkungsausdruck mehrmals in derselben Match-Anweisung vorkommt, kann der Interpreter den ersten berechneten Wert zwischenspeichern und wiederverwenden, anstatt die Auswertung des Ausdrucks zu wiederholen. (Wie bei den Wertmustern von PEP 634 ist dieser Cache streng an eine gegebene Ausführung einer gegebenen Match-Anweisung gebunden.)

Im Gegensatz zu Literalmustern in PEP 634 erfordert dieses PEP, dass komplexe Literale geklammert werden, um vom Parser akzeptiert zu werden. Siehe den Abschnitt "Deferred Ideas" für die Diskussion dieses Punktes.

Wenn dieses PEP stattdessen für PEP 634 übernommen würde, würden alle Literal- und Wertmuster stattdessen expliziter als Wertbeschränkungen geschrieben werden.

# 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?"

Beachten Sie das Beispiel == preferred_side: Die Verwendung eines expliziten Präfixmarkers für Beschränkungsausdrücke hebt die Einschränkung auf, nur mit Attributen oder Literalen für Wert-Lookups zu arbeiten.

Das Beispiel == (1-1j) illustriert die Verwendung von Klammern, um jeden Teilausdruck in einen geschlossenen umzuwandeln.

Wildcard-Muster

Oberflächensyntax

wildcard_pattern: "__"

Abstrakte Syntax

MatchAlways

Ein Wildcard-Muster ist immer erfolgreich. Wie in PEP 634 bindet es keinen Namen.

Während PEP 634 den einzelnen Unterstrich als Wildcard-Muster zur Konsistenz mit anderen Sprachen wählt, wählt dieses PEP den doppelten Unterstrich, da dieser einen klareren Weg hat, potenziell sprachweit konsistent gemacht zu werden, während dieser Weg für "_" durch i18n-bezogene Anwendungsfälle blockiert ist.

Beispielhafte Nutzung

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

Gruppenmuster

Oberflächensyntax

group_pattern: '(' open_pattern ')'

Für die Syntax von open_pattern siehe Muster oben.

Ein geklammertes Muster hat keine zusätzliche Syntax und wird im Abstract Syntax Tree nicht dargestellt. Es erlaubt Benutzern, Klammern um Muster zu setzen, um die beabsichtigte Gruppierung hervorzuheben und verschachtelte offene Muster zu erlauben, wenn die Grammatik ein geschlossenes Muster erfordert.

Im Gegensatz zu PEP 634 gibt es keine potenzielle Mehrdeutigkeit mit Sequenzmustern, da dieses PEP verlangt, dass alle Sequenzmuster mit eckigen Klammern geschrieben werden.

Strukturelle Einschränkungen

Oberflächensyntax

structural_constraint:
    | sequence_constraint
    | mapping_constraint
    | attrs_constraint
    | class_constraint

Hinweis: Die separate Unterkategorie "strukturelle Einschränkung" wird nicht im Abstract Syntax Tree verwendet, sondern dient lediglich als praktischer Gruppierungsknoten in der Oberflächensyntaxdefinition.

Strukturelle Einschränkungen sind Muster, die verwendet werden, um sowohl Aussagen über komplexe Objekte zu machen als auch Werte daraus zu extrahieren.

Diese Muster können alle mehrere Werte binden, entweder durch die Verwendung verschachtelter AS-Muster oder durch die Verwendung von pattern_as_clause-Elementen, die in die Definition des Musters aufgenommen wurden.

Sequenzbeschränkungen

Oberflächensyntax

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

Abstrakte Syntax

MatchSequence(pattern* patterns)

MatchRestOfSequence(identifier? target)

Sequenzbeschränkungen erlauben die Überprüfung von Elementen innerhalb einer Sequenz und deren optionale Extraktion.

Ein Sequenzmuster schlägt fehl, wenn der Subjektwert keine Instanz von collections.abc.Sequence ist. Es schlägt auch fehl, wenn der Subjektwert eine Instanz von str, bytes oder bytearray ist (siehe Deferred Ideas für eine Diskussion über die mögliche Beseitigung dieser Sonderbehandlung).

Ein Sequenzmuster darf höchstens ein Stern-Unter-Muster enthalten. Das Stern-Unter-Muster kann an jeder Position auftreten und wird im AST mit dem Knoten MatchRestOfSequence dargestellt.

Wenn kein Stern-Unter-Muster vorhanden ist, ist das Sequenzmuster ein Sequenzmuster mit fester Länge; andernfalls ist es ein Sequenzmuster mit variabler Länge.

Ein Sequenzmuster mit fester Länge schlägt fehl, wenn die Länge der Subjektsequenz nicht der Anzahl der Unter-Muster entspricht.

Ein Sequenzmuster mit variabler Länge schlägt fehl, wenn die Länge der Subjektsequenz kleiner ist als die Anzahl der Nicht-Stern-Unter-Muster.

Die Länge der Subjektsequenz wird mit der eingebauten Funktion len() ermittelt (d. h. über das __len__-Protokoll). Der Interpreter kann diesen Wert jedoch ähnlich wie bei Beschränkungsausdrücken zwischenspeichern.

Ein Sequenzmuster mit fester Länge gleicht die Unter-Muster den entsprechenden Elementen der Subjektsequenz von links nach rechts ab. Das Abgleichen stoppt (mit einem Fehlschlag), sobald ein Unter-Muster fehlschlägt. Wenn alle Unter-Muster erfolgreich mit ihrem entsprechenden Element abgeglichen werden, ist das Sequenzmuster erfolgreich.

Ein Sequenzmuster mit variabler Länge gleicht zuerst die führenden Nicht-Stern-Unter-Muster mit den entsprechenden Elementen der Subjektsequenz ab, wie bei einem Sequenzmuster mit fester Länge. Wenn dies erfolgreich ist, gleicht das Stern-Unter-Muster einer Liste, die aus den verbleibenden Subjektelementen gebildet wird, wobei Elemente vom Ende entsprechend den Nicht-Stern-Unter-Mustern nach dem Stern-Unter-Muster entfernt werden. Die verbleibenden Nicht-Stern-Unter-Muster werden dann mit den entsprechenden Subjektelementen abgeglichen, wie bei einem Sequenzmuster mit fester Länge.

Unter-Muster müssen meistens geschlossene Muster sein, aber Klammern können für Wertbeschränkungen weggelassen werden. Sequenzelemente können auch bedingungslos ohne Klammern erfasst werden.

Hinweis: Während PEP 634 die gleiche syntaktische Flexibilität wie beim iterierbaren Entpacken in Zuweisungsanweisungen erlaubt, beschränkt dieses PEP Sequenzmuster speziell auf die eckige Klammerform. Da die offenen und geklammerten Formen für das iterierbare Entpacken weitaus beliebter sind als eckige Klammern, unterstreicht dies, dass iterierbares Entpacken und Sequenzabgleich nicht dieselbe Operation sind. Es vermeidet auch das Mehrdeutigkeitsproblem der geklammerten Form zwischen einzelnen Sequenzelementmustern und Gruppenmustern.

Mapping-Beschränkungen

Oberflächensyntax

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

(Beachten Sie, dass **__ durch diese Syntax bewusst nicht erlaubt ist, da zusätzliche Mapping-Einträge standardmäßig ignoriert werden)

closed_expr ist oben unter Wertbeschränkungen definiert.

Abstrakte Syntax

MatchMapping(expr* keys, pattern* patterns)

Mapping-Beschränkungen erlauben die Überprüfung von Schlüsseln und Werten innerhalb einer Sequenz und die optionale Extraktion von Werten.

Ein Mapping-Muster schlägt fehl, wenn der Subjektwert keine Instanz von collections.abc.Mapping ist.

Ein Mapping-Muster ist erfolgreich, wenn jeder im Mapping-Muster angegebene Schlüssel im Subjekt-Mapping vorhanden ist und das Muster für jeden Schlüssel mit dem entsprechenden Element des Subjekt-Mappings übereinstimmt.

Die Anwesenheit von Schlüsseln wird mit der Zwei-Argument-Form der get-Methode und einem eindeutigen Sentinel-Wert überprüft, was folgende Vorteile bietet:

  • Es müssen keine Ausnahmen im Lookup-Prozess erstellt werden.
  • Mappings, die __missing__ implementieren (wie collections.defaultdict), passen nur auf Schlüssel, die sie bereits enthalten; sie fügen keine Schlüssel implizit hinzu.

Ein Mapping-Muster darf keine doppelten Schlüsselwerte enthalten. Wenn Duplikate von Schlüsseln beim Überprüfen des Mapping-Musters erkannt werden, gilt das Muster als ungültig und es wird ein ValueError ausgelöst. Obwohl es theoretisch möglich wäre, Duplikate von konstanten Schlüsseln zur Kompilierzeit zu überprüfen, gibt es derzeit keine solche Prüfung und keine Implementierung.

(Hinweis: Diese semantische Beschreibung leitet sich von der Referenzimplementierung von PEP 634 ab, die zum Zeitpunkt des Schreibens vom Spezifikationstext von PEP 634 abweicht. Die Implementierung erscheint vernünftig, daher scheint die Anpassung des PEP-Textes der beste Weg zu sein, die Diskrepanz zu beheben)

Wenn ein '**' as NAME Doppelsternmuster vorhanden ist, wird dieser Name an ein dict gebunden, das alle verbleibenden Schlüssel-Wert-Paare aus dem Subjekt-Mapping enthält (das Dictionary ist leer, wenn keine zusätzlichen Schlüssel-Wert-Paare vorhanden sind).

Ein Mapping-Muster darf höchstens ein Doppelsternmuster enthalten, und dieses muss an letzter Stelle stehen.

Wert-Submuster müssen größtenteils geschlossene Muster sein, aber die Klammern können für Wertbeschränkungen weggelassen werden (der : Schlüssel/Wert-Separator ist weiterhin erforderlich, um sicherzustellen, dass der Eintrag nicht wie eine gewöhnliche Vergleichsoperation aussieht).

Mapping-Werte können auch bedingungslos mit der Form KEY as NAME erfasst werden, ohne Klammern oder den : Schlüssel/Wert-Separator.

Instanzattributbeschränkungen

Oberflächensyntax

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

Abstrakte Syntax

MatchAttrs(expr cls, identifier* attrs, pattern* patterns)

Instanzattributbeschränkungen ermöglichen die Überprüfung des Typs einer Instanz und die optionale Extraktion von Attributen.

Eine Instanzattributbeschränkung darf denselben Attributnamen nicht mehrmals wiederholen. Versuche, dies zu tun, führen zu einem Syntaxfehler.

Ein Instanzattributmuster schlägt fehl, wenn das Subjekt keine Instanz von name_or_attr ist. Dies wird mit isinstance() getestet.

Wenn name_or_attr keine Instanz des integrierten type ist, wird TypeError ausgelöst.

Wenn keine Attribut-Submuster vorhanden sind, gelingt die Beschränkung, wenn die isinstance() Prüfung erfolgreich ist. Andernfalls

  • Jeder gegebene Attributname wird als Attribut des Subjekts nachgeschlagen.
    • Wenn dies eine andere Ausnahme als AttributeError auslöst, wird die Ausnahme weitergegeben.
    • Wenn dies AttributeError auslöst, schlägt die Beschränkung fehl.
    • Andernfalls wird das mit dem Schlüsselwort assoziierte Submuster gegen den Attributwert abgeglichen. Wenn kein Submuster angegeben ist, wird das Wildcard-Muster angenommen. Wenn dies fehlschlägt, schlägt die Beschränkung fehl. Wenn es erfolgreich ist, geht der Abgleich zum nächsten Attribut über.
  • Wenn alle Attribut-Submuster erfolgreich sind, ist die gesamte Beschränkung erfolgreich.

Instanzattributbeschränkungen ermöglichen die Implementierung von Duck-Typing-Prüfungen durch die Verwendung von object als erforderlichen Instanztyp (z. B. case object{.host as host, .port as port}:).

Die hier vorgeschlagene Syntax könnte potenziell auch als Grundlage für eine neue Syntax zum Abrufen mehrerer Attribute von einer Objektinstanz in einer einzigen Zuweisungsanweisung dienen (z. B. host, port = addr{.host, .port}). Siehe den Abschnitt "Deferred Ideas" für weitere Diskussionen zu diesem Punkt.

Klassendefinierte Beschränkungen

Oberflächensyntax

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] '}'

Abstrakte Syntax

MatchClass(expr cls, pattern* patterns, identifier* extra_attrs, pattern* extra_patterns)

Klassendefinierte Beschränkungen ermöglichen die Angabe einer Sequenz gemeinsamer Attribute in einer Klasse und deren positionale Überprüfung, anstatt die Attributnamen in jedem zugehörigen Abgleichsmuster angeben zu müssen.

Wie bei Instanzattributmustern

  • ein klassendefiniertes Muster schlägt fehl, wenn das Subjekt keine Instanz von name_or_attr ist. Dies wird mit isinstance() getestet.
  • wenn name_or_attr keine Instanz des integrierten type ist, wird TypeError ausgelöst.

Unabhängig davon, ob Argumente vorhanden sind oder nicht, wird das Subjekt auf ein __match_args__ Attribut geprüft, mittels des Äquivalents von getattr(cls, "__match_args__", _SENTINEL)).

Wenn dies eine Ausnahme auslöst, wird die Ausnahme weitergegeben.

Wenn der zurückgegebene Wert keine Liste, kein Tupel oder None ist, schlägt die Konvertierung fehl und TypeError wird zur Laufzeit ausgelöst.

Das bedeutet, dass nur Typen, die tatsächlich __match_args__ definieren, in klassendefinierten Mustern verwendet werden können. Typen, die __match_args__ nicht definieren, können weiterhin in Instanzattributmustern verwendet werden.

Wenn __match_args__ None ist, ist nur ein einziges positionales Submuster zulässig. Der Versuch, zusätzliche Attributmuster entweder positionell oder mithilfe der Doppelsternsyntax anzugeben, führt zur Laufzeit zu TypeError.

Dieses positionale Submuster wird dann gegen das gesamte Subjekt abgeglichen, wodurch eine Typenprüfung mit einem anderen Abgleichsmuster kombiniert werden kann (z. B. Überprüfung von Typ und Inhalt eines Containers oder Typ und Wert einer Zahl).

Wenn __match_args__ eine Liste oder ein Tupel ist, wird die klassendefinierte Beschränkung wie folgt in eine Instanzattributbeschränkung umgewandelt:

  • Wenn nur das Doppelstern-Attributbeschränkungs-Submuster vorhanden ist, geht der Abgleich wie für die entsprechende Instanzattributbeschränkung vor.
  • Wenn es mehr positionale Submuster als die Länge von __match_args__ (ermittelt mit len()) gibt, wird TypeError ausgelöst.
  • Andernfalls wird das positionale Muster i mithilfe von __match_args__[i] als Attributname in ein Attributmuster umgewandelt.
  • Wenn ein Element in __match_args__ keine Zeichenkette ist, wird TypeError ausgelöst.
  • Sobald die positionalen Muster in Attributmuster umgewandelt wurden, werden sie mit allen Attributbeschränkungen kombiniert, die im Doppelstern-Attributbeschränkungs-Submuster angegeben sind, und der Abgleich erfolgt wie für die entsprechende Instanzattributbeschränkung.

Hinweis: Die Behandlung von __match_args__ is None in diesem PEP ersetzt die Sonderbehandlung von bool, bytearray, bytes, dict, float, frozenset, int, list, set, str und tuple in PEP 634. Der optimierte Fast-Path für diese Typen bleibt jedoch in der Implementierung erhalten.

Design-Diskussion

Erfordert explizite Qualifizierung einfacher Namen in Match-Mustern

Die erste Iteration dieses PEP akzeptierte die grundlegende Prämisse von PEP 634, dass die Syntax für iterierbare Entpackung eine gute Grundlage für die Definition einer neuen Syntax für Pattern Matching bieten würde.

Während des Überprüfungsprozesses wurden jedoch zwei Haupt- und eine kleinere Mehrdeutigkeitsprobleme hervorgehoben, die sich direkt aus dieser Kernannahme ergeben.

  • Am problematischsten ist, dass bei der standardmäßigen Bindung einfacher Namen, die auf die vorgeschlagene Klassensyntax von PEP 634 erweitert wird, der Konstrukt ATTR=TARGET_NAME an die rechte Seite bindet, ohne das Schlüsselwort as zu verwenden, und das normale Zuweisungs-an-die-linke-Sigil (=) verwendet, um dies zu tun!
  • Wenn die standardmäßige Bindung einfacher Namen auf die vorgeschlagene Mapping-Syntax von PEP 634 erweitert wird, bindet der Konstrukt KEY: TARGET_NAME an die rechte Seite, ohne das Schlüsselwort as zu verwenden.
  • Die Verwendung eines PEP 634 Capture-Musters zusammen mit einem AS-Muster (TARGET_NAME_1 as TARGET_NAME_2) ergibt ein seltsames Verhalten, das "sowohl links als auch rechts bindet".

Die dritte Überarbeitung dieses PEP trug diesem Problem Rechnung, indem sie die Ausrichtung an der Syntax für iterierbare Entpackung aufgab und stattdessen verlangte, dass alle Verwendungen von leeren einfachen Namen für etwas anderes als eine Variablen-Lookups durch ein vorangestelltes Sigil oder Schlüsselwort qualifiziert werden müssen.

  • as NAME: lokale Variablenbindung
  • .NAME: Attribut-Lookup
  • == NAME: Variablen-Lookup
  • is NAME: Variablen-Lookup
  • Jede andere Verwendung: Variablen-Lookup

Der Hauptvorteil dieses Ansatzes besteht darin, dass die Interpretation einfacher Namen in Mustern eine lokale Aktivität ist: ein führendes as zeigt eine Namensbindung an, ein führendes . zeigt einen Attribut-Lookup an, und alles andere ist ein Variablen-Lookup (unabhängig davon, ob wir ein Submuster oder einen Subausdruck lesen).

Mit der nun in diesem PEP vorgeschlagenen Syntax lesen sich die oben identifizierten problematischen Fälle nicht mehr schlecht.

  • .ATTR as TARGET_NAME ist offensichtlicher eine Bindung als ATTR=TARGET_NAME
  • KEY as TARGET_NAME ist offensichtlicher eine Bindung als KEY: TARGET_NAME
  • (as TARGET_NAME_1) as TARGET_NAME_2 sind offensichtlicher zwei Bindungen als TARGET_NAME_1 as TARGET_NAME_2

Widerstehe der Versuchung zu raten

PEP 635 untersucht die Art und Weise, wie Pattern Matching in anderen Sprachen verwendet wird, und versucht, diese Informationen zu nutzen, um plausible Vorhersagen darüber zu treffen, wie Pattern Matching in Python verwendet werden wird.

  • Der Wunsch, Werte in lokale Namen zu extrahieren, wird *wahrscheinlich* häufiger vorkommen als der Wunsch, gegen in lokalen Namen gespeicherte Werte abzugleichen.
  • Der Wunsch nach einem Vergleich per Gleichheit wird *wahrscheinlich* häufiger vorkommen als der Wunsch nach einem Vergleich per Identität.
  • Benutzer werden *wahrscheinlich* zumindest in der Lage sein, sich daran zu erinnern, dass leere Namen Werte binden und Attributreferenzen Werte nachschlagen, selbst wenn sie das nicht selbst herausfinden können, ohne die Dokumentation zu lesen oder jemandem davon zu erzählen.

Um es klar auszudrücken: Ich halte diese Vorhersagen tatsächlich für plausibel. Ich glaube jedoch auch nicht, dass wir im Voraus raten müssen: Ich denke, wir können mit einer expliziteren Syntax beginnen, die von den Benutzern verlangt, ihre Absicht mithilfe eines Präfix-Markers anzugeben (entweder as, == oder is), und dann die Situation in ein paar Jahren neu bewerten, basierend darauf, wie Pattern Matching tatsächlich *in Python* verwendet wird.

Zu diesem Zeitpunkt können wir aus mindestens den folgenden Optionen wählen:

  • die Entscheidung, dass die explizite Syntax prägnant genug ist und nichts geändert wird.
  • Hinzufügen von inferierten Identitätsbeschränkungen für eines oder mehrere der folgenden Elemente: None, ..., True und False.
  • Hinzufügen von inferierten Gleichheitsbeschränkungen für andere Literale (potenziell einschließlich komplexer Literale).
  • Hinzufügen von inferierten Gleichheitsbeschränkungen für Attribut-Lookups.
  • Hinzufügen von entweder inferierten Gleichheitsbeschränkungen oder inferierten Capture-Mustern für leere Namen.

Alle diese Ideen könnten unabhängig voneinander nach ihren eigenen Verdiensten betrachtet werden, anstatt eine potenzielle Barriere für die Einführung von Pattern Matching darzustellen.

Wenn einige dieser syntaktischen Abkürzungen schließlich eingeführt würden, wären sie auch einfach zu erklären im Hinblick auf die zugrunde liegende explizitere Syntax (das führende as, == oder is würde einfach vom Parser abgeleitet, ohne dass der Benutzer es explizit angeben müsste). Auf Implementierungsebene müsste nur der Parser geändert werden, da die bestehenden AST-Knoten wiederverwendet werden könnten.

Interaktion mit Caching von Attribut-Lookups in lokalen Variablen

Eine der Hauptänderungen zwischen diesem PEP und PEP 634 ist die Verwendung von == EXPR für Gleichheitsbeschränkungs-Lookups, anstatt nur NAME.ATTR anzubieten. Die ursprüngliche Motivation dafür war die Vermeidung des semantischen Konflikts mit regulären Zuweisungszielen, bei denen NAME.ATTR bereits in Zuweisungsanweisungen zur Festlegung von Attributen verwendet wird. Wenn NAME.ATTR die *einzige* Syntax für symbolisches Wert-Matching wäre, dann schließen wir präemptiv alle zukünftigen Versuche aus, gegen einzelne Muster mit der bestehenden Zuweisungsanweisungssyntax abzugleichen. Die aktuelle Motivation liegt eher im allgemeinen Wunsch, nicht über die Absicht der Benutzer zu raten, sondern sie stattdessen explizit in der Syntax angeben zu lassen.

Doch selbst innerhalb von Match-Anweisungen hat die name.attr Syntax für Wertmuster eine unerwünschte Wechselwirkung mit der lokalen Variablenzuweisung, bei der routinemäßige Refactorings, die für jede andere Python-Anweisung semantisch neutral wären, eine wesentliche semantische Änderung einführen, wenn sie auf eine PEP 634-Stil Match-Anweisung angewendet werden.

Betrachten Sie den folgenden Code

while value < self.limit:
    ... # Some code that adjusts "value"

Der Attribut-Lookup kann sicher aus der Schleife herausgehoben und nur einmal durchgeführt werden

_limit = self.limit:
while value < _limit:
    ... # Some code that adjusts "value"

Mit dem in diesem PEP vorgeschlagenen Marker-Präfix-basierten Syntaxansatz wären Wertbeschränkungen ähnlich tolerant gegenüber der Refaktorierung von Match-Mustern zur Verwendung einer lokalen Variable anstelle eines Attribut-Lookups, wobei die folgenden beiden Anweisungen funktional äquivalent wären.

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

Im Gegensatz dazu wären bei der Verwendung der Wert- und Capture-Muster von PEP 634, die das Marker-Präfix weglassen, die folgenden beiden Anweisungen nicht äquivalent.

# 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

Dieses PEP stellt sicher, dass die ursprüngliche Semantik bei dieser Art von simplistischer Refaktorierung erhalten bleibt: Verwenden Sie == name, um die Interpretation des Ergebnisses als Wertbeschränkung zu erzwingen, und as name für eine Namensbindung.

PEP 634's Vorschlag, nur die Kurzschreibweise ohne explizites Präfix anzubieten, bedeutet, dass die primäre Antwort "Nun, tun Sie das dann nicht, vergleichen Sie nur mit Attributen in Namespaces, vergleichen Sie nicht mit einfachen Namen" lautet.

PEP 622's Walross-Muster-Syntax hatte eine weitere seltsame Wechselwirkung, bei der sie nicht dasselbe Objekt wie der exakt gleiche Walross-Ausdruck im Körper der Case-Klausel binden konnte, aber PEP 634 hat diese Diskrepanz behoben, indem Walross-Muster durch AS-Muster ersetzt wurden (wobei die Tatsache, dass der an den Namen auf der RHS gebundene Wert nicht derselbe Wert ist wie der von der LHS zurückgegebene, ein Standardmerkmal aller Verwendungen des Schlüsselworts "as" ist).

Verwendung bestehender Vergleichsoperatoren als Präfix für Wertbeschränkungen

Wenn der Nutzen eines dedizierten Präfixes für Wertbeschränkungen akzeptiert wird, ist die nächste Frage, welches Präfix genau das sein sollte.

Die erste veröffentlichte Version dieses PEP schlug die bisher ungenutzte ?-Symbol als Präfix für Gleichheitsbeschränkungen und ?is als Präfix für Identitätsbeschränkungen vor. Bei der Überprüfung des PEP legte Steven D’Aprano einen überzeugenden Gegenvorschlag [5] vor, stattdessen die bestehenden Vergleichsoperatoren (== und is) zu verwenden.

Es gab einige Bedenken hinsichtlich == als Präfix, die es davon abhielten, in der ersten Iteration des PEP als Präfix gewählt zu werden.

  • Für gängige Anwendungsfälle ist es sogar noch visuell störender als ?, da viele Leute mit PEP 8-geschulten ästhetischen Empfindlichkeiten ein Leerzeichen zwischen ihm und dem folgenden Ausdruck setzen möchten, was es effektiv zu einem 3-Zeichen-Präfix anstelle eines 1-Zeichens macht.
  • Wenn es in einem Mapping-Muster verwendet wird, muss ein Leerzeichen zwischen dem : Schlüssel/Wert-Separator und dem == Präfix liegen, sonst wird der Tokenizer sie falsch trennen (es ergibt sich := und = statt : und ==).
  • Wenn es in einem OR-Muster verwendet wird, muss ein Leerzeichen zwischen dem | Muster-Separator und dem == Präfix liegen, sonst wird der Tokenizer sie falsch trennen (es ergibt sich |= und = statt | und ==).
  • Wenn es in einem PEP 634-Stil Klassenmuster verwendet wird, muss ein Leerzeichen zwischen dem = Schlüsselwort-Separator und dem == Präfix liegen, sonst wird der Tokenizer sie falsch trennen (es ergibt sich == und = statt = und ==).

Anstatt ein völlig neues Symbol einzuführen, bestand Stevens vorgeschlagene Lösung für dieses Ausführlichkeitsproblem darin, die Möglichkeit beizubehalten, das Präfix-Marker in syntaktisch eindeutigen Fällen wegzulassen.

Während die Idee, das Präfix-Marker wegzulassen, für die zweite Revision des Vorschlags akzeptiert wurde, wurde sie in der dritten Revision aufgrund von Mehrdeutigkeitsbedenken wieder verworfen. Stattdessen gelten die folgenden Punkte:

  • Für Klassenmuster erlauben andere Syntaxänderungen, Gleichheitsbeschränkungen als .ATTR == EXPR und Identitätsbeschränkungen als .ATTR is EXPR zu schreiben, beides ist sehr gut lesbar.
  • Für Mapping-Muster wird der zusätzliche syntaktische Lärm toleriert (zumindest vorerst).
  • Für OR-Muster wird der zusätzliche syntaktische Lärm toleriert (zumindest vorerst). Mitgliedschaftsbeschränkungen können jedoch einen zukünftigen Weg bieten, um die Notwendigkeit, OR-Muster mit Gleichheitsbeschränkungen zu kombinieren, zu reduzieren (stattdessen würden die zu prüfenden Werte als Set, Liste oder Tupel gesammelt).

Aus dieser Perspektive galten die Argumente von PEP 635 gegen die Verwendung von ? als Teil der Pattern Matching-Syntax auch für diesen Vorschlag, und so wurde der PEP entsprechend angepasst.

Verwendung von __ als Wildcard-Muster-Marker

PEP 635 liefert einen soliden Grund dafür, dass die Einführung von ? *ausschließlich* als Wildcard-Muster-Marker eine schlechte Idee wäre. Da die Syntax für Wertbeschränkungen von der Verwendung bestehender Vergleichsoperatoren anstelle von ? und ?is auf die Verwendung bestehender Vergleichsoperatoren geändert wurde, gilt dieses Argument auch für diesen PEP.

Wie jedoch von Thomas Wouters in [6] angemerkt, bleibt die Wahl von _ in PEP 634 problematisch, da dies wahrscheinlich bedeutet, dass Match-Muster einen *dauerhaften* Unterschied zu allen anderen Teilen von Python haben würden – die Verwendung von _ in Software-Internationalisierung und an der interaktiven Eingabeaufforderung bedeutet, dass es keinen plausiblen Weg gibt, sie als generischen "übersprungenen Bindungs"-Marker zu verwenden.

__ ist ein alternativer "Dieser Wert wird nicht benötigt"-Marker aus einer Stack Overflow-Antwort [7] (ursprünglich vom Autor dieses PEP gepostet) über die verschiedenen Bedeutungen von _ im bestehenden Python-Code.

Dieser PEP schlägt auch die Übernahme einer Implementierungstechnik vor, die den Geltungsbereich der damit verbundenen Sonderbehandlung von __ auf den Parser beschränkt: Definition eines neuen AST-Knotentyps (MatchAlways) speziell für Wildcard-Marker, anstatt ihn als Name-Knoten an den AST weiterzugeben.

Innerhalb des Parsers bedeutet __ entweder einen regulären Namen oder einen Wildcard-Marker in einem Match-Muster, je nachdem, wo man sich im Parse-Baum befand, aber innerhalb des restlichen Compilers ist Name("__") immer noch ein normaler Variablenname, während MatchAlways() immer ein Wildcard-Marker in einem Match-Muster ist.

Im Gegensatz zu _ gibt es für __ keine anderen Anwendungsfälle, so dass ein plausibler Weg zur Wiederherstellung der Bezeichnerbehandlungs-Konsistenz mit dem Rest der Sprache besteht, indem __ überall in Python für "diese Namensbindung überspringen" steht.

  • Im Interpreter selbst wird das Laden von Variablen mit dem Namen __ als veraltet markiert. Dies würde dazu führen, dass das Lesen von __ eine Verwarnung ausgibt, während das Schreiben darauf zunächst unverändert bleibt. Um nicht alle Namensladungen zu verlangsamen, könnte dies dadurch erreicht werden, dass der Compiler zusätzlichen Code für den veralteten Namen erzeugt, anstatt eine Laufzeitprüfung in den Standard-Namensladungs-Opcodes durchzuführen.
  • Nach einer angemessenen Anzahl von Releases wird der Parser geändert, um einen neuen SkippedBinding AST-Knoten für alle Verwendungen von __ als Zuweisungsziel zu erzeugen und den Rest des Compilers entsprechend zu aktualisieren.
  • Erwägen Sie, __ zu einem echten Hard-Keyword zu machen und nicht zu einem Soft-Keyword.

Dieser Deprecationsweg konnte für _ nicht verfolgt werden, da es keine Möglichkeit gibt, dass der Interpreter zwischen Versuchen, _ zu lesen, wenn es nominell als "egal"-Marker verwendet wird, und legitimen Lesevorgängen von _ als i18n-Textübersetzungsfunktion oder als letztes Statement-Ergebnis an der interaktiven Eingabeaufforderung unterscheidet.

Namen, die mit doppelten Unterstrichen beginnen, sind bereits für die Verwendung durch die Sprache reserviert, sei es für Compile-Time-Konstanten (d. h. __debug__), spezielle Methoden oder das Namens-Mangling von Klassenattributen, so dass die Verwendung von __ hier mit diesem bestehenden Ansatz übereinstimmen würde.

Muster explizit im Abstract Syntax Tree darstellen

PEP 634 diskutiert nicht explizit, wie Match-Anweisungen im Abstract Syntax Tree (AST) dargestellt werden sollen, und überlässt diese Details der Implementierung.

Infolgedessen, obwohl die Referenzimplementierung von PEP 634 definitiv funktioniert (und die Grundlage für die Referenzimplementierung dieses PEP bildete), enthält sie einen signifikanten Designfehler: Trotz der Hinweise in PEP 635, dass Muster als von Ausdrücken getrennt betrachtet werden sollten, stellt die Referenzimplementierung sie als Ausdrucksknoten im AST dar.

Das Ergebnis ist ein AST, der gar nicht so abstrakt ist: Knoten, die völlig unterschiedlich kompiliert werden sollten (da sie Muster und keine Ausdrücke sind), werden auf die gleiche Weise dargestellt, und das Typsystem der Implementierungssprache (z. B. C für CPython) kann keine Hilfe bei der Verfolgung bieten, welche Unterknoten gewöhnliche Ausdrücke und welche Submuster sein sollten.

Anstatt diesen Ansatz fortzusetzen, hat dieser PEP stattdessen einen neuen expliziten "Pattern"-Knoten im AST definiert, der es ermöglicht, die Muster und ihre zulässigen Unterknoten explizit im AST selbst zu definieren, wodurch der Code, der die neue Funktion implementiert, klarer wird und der C-Compiler mehr Unterstützung bei der Verfolgung bietet, wann der Codegenerator mit Mustern oder Ausdrücken umgeht.

Diese Änderung im Implementierungsansatz ist tatsächlich orthogonal zu den Oberflächensyntaxänderungen, die in diesem PEP vorgeschlagen werden, so dass sie auch dann noch übernommen werden könnte, wenn der Rest des PEP abgelehnt würde.

Änderungen an Sequenzmustern

Dieses PEP nimmt im Vergleich zu PEP 634 eine bemerkenswerte Änderung an Sequenzmustern vor.

  • Es wird nur die eckige Klammerform des Sequenzmusters unterstützt. Weder offene (keine Begrenzer) noch Tupel-Stil (Klammern als Begrenzer) Sequenzmuster werden unterstützt.

Im Vergleich zu PEP 634 sind Sequenzmuster ebenfalls erheblich von der Änderung betroffen, die eine explizite Qualifizierung von Erfassungsmustern und Wertbeschränkungen erfordert. Dies bedeutet, dass case [a, b, c]: stattdessen als case [as a, as b, as c]: geschrieben werden muss und case [0, 1]: stattdessen als case [== 0, == 1]: geschrieben werden muss.

Da die Syntax für Sequenzmuster nicht mehr direkt aus der Syntax für Iterierbare-Entpackung abgeleitet wird, war es nicht mehr sinnvoll, die syntaktische Flexibilität beizubehalten, die in dem ursprünglichen Syntaxvorschlag rein zur Konsistenz mit der Iterierbare-Entpackung enthalten war.

Die Zulassung von offenen und Tupel-artigen Sequenzmustern erhöhte nicht die Ausdruckskraft, sondern nur die Mehrdeutigkeit der Absicht (insbesondere im Vergleich zu Gruppenmustern) und verleitete die Leser dazu, die Syntax für Musterabgleich als intrinsisch mit der Syntax für Zuweisungsziele verbunden zu betrachten (was die Autoren von PEP 634 mehrfach als unerwünschten Weg bezeichnet haben, den Leser einschlagen, und eine Ansicht, die der Autor dieses PEP nun teilt, obwohl er ihr ursprünglich nicht zustimmte).

Änderungen an Mapping-Mustern

Dieses PEP nimmt zwei bemerkenswerte Änderungen an Zuordnungsmustern im Vergleich zu PEP 634 vor:

  • Wert-Erfassung wird als SCHLÜSSEL as NAME geschrieben, anstatt als SCHLÜSSEL: NAME.
  • Eine breitere Palette von Schlüsseln ist zulässig: jeder "geschlossene Ausdruck" anstelle von nur Literalen und Attributreferenzen.

Wie oben diskutiert, dient die erste Änderung dazu, sicherzustellen, dass alle Bindungsoperationen mit dem Zielnamen rechts von einem Unterausdruck oder Muster das Schlüsselwort as verwenden.

Die zweite Änderung ist hauptsächlich eine Vereinfachung des Parser- und Codegenerator-Codes durch Wiederverwendung der vorhandenen Ausdruckshandhabungsmechanismen. Die Einschränkung auf geschlossene Ausdrücke soll dazu beitragen, die Mehrdeutigkeit zu reduzieren, wo der Schlüssel-Ausdruck endet und das Musterabgleich beginnt. Dies erlaubt meist eine Obermenge dessen, was PEP 634 erlaubt, mit der Ausnahme, dass komplexe Literale in Klammern geschrieben werden müssen (zumindest vorerst).

Anpassung der Zuordnungsmuster-Beispiele von PEP 635 an die in diesem PEP vorgeschlagene Syntax.

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)

Zur Referenz die äquivalente PEP 634-Syntax.

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)

Änderungen an Klassmustern

Dieses PEP nimmt mehrere bemerkenswerte Änderungen an Klassenmustern im Vergleich zu PEP 634 vor:

  • Die syntaktische Ausrichtung an der Klasseninstanziierung wird als aktiv irreführend und wenig hilfreich aufgegeben. Stattdessen wird eine neue dedizierte Syntax zur Überprüfung zusätzlicher Attribute eingeführt, die sich an Zuordnungsmustern orientiert und nicht an der Klasseninstanziierung.
  • Eine neue dedizierte Syntax für einfaches "Duck-Typing", das für jede Klasse funktioniert, wird eingeführt.
  • Die spezielle Behandlung verschiedener integrierter und Standardbibliothekstypen wird durch eine allgemeine Prüfung auf die Existenz eines __match_args__-Attributs mit dem Wert None ergänzt.

Wie oben diskutiert, hat die erste Änderung zwei Zwecke:

  • Sie dient dazu, sicherzustellen, dass alle Bindungsoperationen mit dem Zielnamen rechts von einem Unterausdruck oder Muster das as-Schlüsselwort verwenden. Die Verwendung von = zur Zuweisung auf der rechten Seite ist besonders problematisch.
  • Sie dient dazu, sicherzustellen, dass alle Verwendungen einfacher Namen in Mustern ein Präfix haben, das ihren Zweck anzeigt (in diesem Fall ein führender ., um eine Attributsuche anzuzeigen).

Die syntaktische Ausrichtung an der Klasseninstanziierung wurde ebenfalls generell als wenig hilfreich beurteilt, da Klassenmuster dem Abgleichen von Attributen gegen Muster dienen, während Klasseninstanziierung dem Abgleichen von Aufrufargumenten mit Parametern in Klassenkonstruktoren dient, die überhaupt keine große Ähnlichkeit mit den resultierenden Instanzattributen haben mögen.

Die zweite Änderung soll die Verwendung von Musterabgleich für die "Duck-Typing"-Stilprüfungen erleichtern, die in Python bereits üblich sind.

Der konkrete Syntaxvorschlag für diese Muster entstand aus der Betrachtung von Instanzen als Zuordnungen von Attributnamen zu Werten und der Kombination der Attributsuchsyntax (.ATTR) mit der Zuordnungsmustersyntax {SCHLÜSSEL: MUSTER}, um cls{.ATTR: MUSTER} zu erhalten.

Die Zulassung von cls{.ATTR} mit der gleichen Bedeutung wie cls{.ATTR: __} war eine Frage der Betrachtung des führenden . als ausreichend zur eindeutigen Auflösung des Namensgebrauchs (es ist eindeutig eine Attributreferenz, während der Abgleich mit einem variablen Schlüssel in einem Zuordnungsmuster wohl mehrdeutig wäre).

Die letzte Änderung ergänzt lediglich eine CPython-interne Prüfung in der Referenzimplementierung von PEP 634, indem sie das Standardverhalten festlegt, das Klassen erhalten, wenn sie __match_args__ nicht definieren (der optimierte Schnelldurchlauf für die in PEP 634 genannten integrierten und Standardbibliothekstypen bleibt erhalten).

Die Anpassung des Klassenabgleichsbeispiels, das in PEP 635 verlinkt ist, zeigt, dass für rein positionellen Klassenabgleich die Hauptwirkung von den Änderungen an Wertbeschränkungen und Namensbindung ausgeht, nicht von den Änderungen am Klassenabgleich.

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)}")

Zur Referenz die äquivalente PEP 634-Syntax.

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)}")

Die Änderungen an der Klassensyntax selbst sind relevanter, wenn benannte Attribute geprüft und ihre Werte extrahiert werden, ohne sich auf __match_args__ zu verlassen.

match expr:
    case object{.host as host, .port as port}:
        pass
    case object{.host as host}:
        pass

Vergleichen Sie dies mit dem PEP 634-Äquivalent, bei dem es nicht wirklich klar ist, welche Namen sich auf Attribute des Abgleichssubjekts beziehen und welche Namen sich auf lokale Variablen beziehen.

match expr:
    case object(host=host, port=port):
        pass
    case object(host=host):
        pass

In diesem speziellen Fall ist diese Mehrdeutigkeit unerheblich (da die Attribut- und Variablennamen gleich sind), aber im allgemeinen Fall ist es entscheidend zu wissen, welche was ist, um korrekt über den gelesenen Code zu urteilen.

Zurückgestellte Ideen

Abgeleitete Wertbeschränkungen

Wie oben diskutiert, schließt dieses PEP die Möglichkeit nicht aus, zukünftig abgeleitete Gleichheits- und Identitätsbeschränkungen hinzuzufügen.

Diese könnten besonders wertvoll für Literale sein, da es sehr wahrscheinlich ist, dass viele "magische" Zeichenketten und Zahlen mit selbsterklärender Bedeutung direkt in Abgleichsmuster geschrieben werden, anstatt in benannten Variablen gespeichert zu werden. (Denken Sie an Konstanten wie None oder offensichtlich spezielle Zahlen wie 0 und 1 oder Zeichenketten, deren Inhalt genauso beschreibend ist wie jeder Variablenname, anstatt kryptische Prüfungen gegen opake Zahlen wie 739452)

Einige erforderliche Klammern optional machen

Das PEP tendiert derzeit stark dazu, Klammern im Angesicht potenzieller Mehrdeutigkeit zu verlangen.

Es gibt jedoch eine Reihe von Fällen, in denen es zumindest streitbar zu weit geht, meistens AS-Muster mit einem expliziten Muster.

An jeder Position, die ein geschlossenes Muster erfordert, können AS-Muster mit doppelten Klammern beginnen, da das verschachtelte Muster ebenfalls ein geschlossenes Muster sein muss: ((OFFENES MUSTER) as NAME)

Aufgrund der Anforderung, dass das Unter-Muster geschlossen sein muss, sollte es in vielen dieser Fälle (z. B. Sequenzmuster-Unter-Muster) vernünftig sein, GESCHLOSSENES_MUSTER as NAME direkt zu akzeptieren.

Weitere Überlegungen zu diesem Punkt wurden zurückgestellt, da das Optionale-Machen von erforderlichen Klammern eine abwärtskompatible Änderung ist und daher das Lockern von Einschränkungen später fallweise betrachtet werden kann.

Akzeptieren komplexer Literale als geschlossene Ausdrücke

PEP 634s Referenzimplementierung enthält viele spezielle Fälle für binäre Operationen sowohl im Parser als auch im restlichen Compiler, um komplexe Literale zu akzeptieren, ohne beliebige binäre numerische Operationen auf Literalwerten zu akzeptieren.

Idealerweise würde dieses Problem auf Parser-Ebene behandelt, wobei der Parser direkt einen Konstanten AST-Knoten ausgibt, der mit einer komplexen Zahl vorab gefüllt ist. Wenn die Dinge so funktionierten, könnten komplexe Literale über einen ähnlichen Mechanismus wie jedes andere Literal akzeptiert werden.

So werden komplexe Literale jedoch nicht behandelt. Stattdessen werden sie an den AST als reguläre BinOp-Knoten weitergegeben, und dann löst der Constant-Folding-Pass auf dem AST sie zu Constant-Knoten mit einem komplexen Wert auf.

Damit der Parser komplexe Literale direkt auflösen kann, müsste der Compiler den Tokenizer anweisen, einen eigenen Token-Typ für imaginäre Zahlen zu generieren (z. B. INUMBER), was es dem Parser ermöglichen würde, ZAHL + INUMBER und ZAHL - INUMBER getrennt von anderen binären Operationen zu behandeln.

Alternativ könnte ein neuer AST-Knoten-Typ ComplexNumber definiert werden, der es dem Parser ermöglichen würde, die nachfolgenden Compiler-Stufen darüber zu informieren, dass ein bestimmter Knoten speziell ein komplexes Literal sein soll, anstatt eine beliebige binäre Operation. Dann könnte der Parser ZAHL + ZAHL und ZAHL - ZAHL für diesen Knoten akzeptieren, während die AST-Validierung für ComplexNumber sicherstellt, dass der reale und der imaginäre Teil des Literals wie erwartet reale und imaginäre Zahlen sind.

Vorerst hat dieses PEP die Behandlung dieser Frage zurückgestellt und verlangt stattdessen, dass komplexe Literale geklammert werden, um sie in Wertbeschränkungen und als Schlüssel von Zuordnungsmustern verwenden zu können.

Zulassen von negierten Beschränkungen in Match-Mustern

Mit der in diesem PEP vorgeschlagenen Syntax ist es nicht erlaubt, != expr oder is not expr als Abgleichmuster zu schreiben.

Beide Formen haben klare potenzielle Interpretationen als negierte Gleichheitsbeschränkung (d. h. x != expr) und als negierte Identitätsbeschränkung (d. h. x is not expr).

Es ist jedoch bei weitem nicht klar, ob eine der beiden Formen oft genug vorkommen würde, um die dedizierte Syntax zu rechtfertigen, so dass die mögliche Erweiterung bis zu weiterer gemeinschaftlicher Erfahrung mit Abgleichanweisungen zurückgestellt wurde.

Zulassen von Mitgliedschaftsprüfungen in Match-Mustern

Die für Gleichheits- und Identitätsbeschränkungen verwendete Syntax wäre einfach auf Mitgliedschaftsprüfungen erweiterbar: in container.

Ein Nachteil der Vorschläge in diesem PEP und in PEP 634 ist, dass die Überprüfung mehrerer Werte im selben Fall nicht wie eine bestehende Container-Mitgliedschaftsprüfung in Python aussieht.

# PEP 634's literal patterns
match value:
    case 0 | 1 | 2 | 3:
        ...

# This PEP's equality constraints
match value:
    case == 0 | == 1 | == 2 | == 3:
        ...

Die Zulassung abgeleiteter Gleichheitsbeschränkungen nach diesem PEP würde es nur so aussehen lassen wie das Beispiel in PEP 634, es würde immer noch nicht wie die äquivalente if-Anweisungsüberschrift aussehen (if wert in {0, 1, 2, 3}:).

Mitgliedschaftsbeschränkungen würden eine explizitere, aber dennoch prägnante Möglichkeit bieten, zu überprüfen, ob das Abgleichssubjekt in einem Container vorhanden ist, und es würde wie eine normale Containment-Prüfung aussehen.

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
        ...

Eine solche Funktion wäre auch leicht erweiterbar, um alle Arten von Fallklauseln ohne weitere Syntaxaktualisierungen zuzulassen, einfach durch geeignete Definition von __contains__ in einer benutzerdefinierten Klassendefinition.

Obwohl dies eine nützliche Erweiterung zu sein scheint und eine gute Möglichkeit, das Ausführlichkeitsproblem dieses PEP bei der Kombination mehrerer Gleichheitsprüfungen in einem OR-Muster zu lösen, ist sie nicht wesentlich, um Abgleichsanweisungen zu einer wertvollen Ergänzung der Sprache zu machen. Daher scheint es angebrachter, sie in einem separaten Vorschlag zu behandeln, anstatt sie hier aufzunehmen.

Ableiten eines Standardtyps für Instanzattributbeschränkungen

Die dedizierte Syntax für Instanzattributbeschränkungen bedeutet, dass object aus object{.ATTR} weggelassen werden könnte, um {.ATTR} zu erhalten, ohne eine syntaktische Mehrdeutigkeit einzuführen (wenn keine Klasse angegeben wäre, würde object impliziert, genau wie bei der Basisklassenliste in Klassendefinitionen).

Es ist jedoch bei weitem nicht klar, ob das Sparen von sechs Zeichen es schwieriger macht, Zuordnungsmuster von Instanzattributmustern visuell zu unterscheiden, so dass die Zulassung dessen als Thema für mögliche zukünftige Überlegungen zurückgestellt wurde.

Vermeidung von Sonderfällen bei Sequenzmustern

Sequenzmuster in diesem PEP und PEP 634 behandeln derzeit str, bytes und bytearray speziell als **niemals** übereinstimmend mit einem Sequenzmuster.

Diese spezielle Behandlung könnte potenziell entfernt werden, wenn wir eine neue abstrakte Basisklasse collections.abc.AtomicSequence für solche Typen definieren würden, bei denen sie konzeptionell ein einzelnes Element sind, aber dennoch das Sequenzprotokoll implementieren, um wahlfreien Zugriff auf ihre Bestandteile zu ermöglichen.

Ausdruckssyntax zum Abrufen mehrerer Attribute von einer Instanz

Die Syntax für Instanzattributmuster ermöglicht es, dass sie als Grundlage für eine allgemeine Syntax zum Abrufen mehrerer Attribute von einem Objekt in einem einzigen Ausdruck verwendet werden kann.

host, port = obj{.host, .port}

Ähnlich wie Slice-Syntax nur innerhalb von Bracket-Subskripts erlaubt ist, wäre die .attr-Syntax für die Benennung von Attributen nur innerhalb von Brace-Subskripts erlaubt.

Diese Idee ist nicht notwendig, damit Musterabgleich nützlich ist, daher ist sie nicht Teil dieses PEP. Sie wird jedoch als möglicher Weg erwähnt, um Musterabgleich stärker in den Rest der Sprache zu integrieren, anstatt ewig in einer völlig getrennten Welt zu existieren.

Ausdruckssyntax zum Abrufen mehrerer Attribute von einer Instanz

Wenn die Brace-Subskript-Syntax für Instanzattributmusterabgleich akzeptiert und dann auf die allgemeine Extraktion mehrerer Attribute erweitert würde, könnte sie noch weiter erweitert werden, um den Abruf mehrerer Elemente aus Containern basierend auf der für Zuordnungsmuster verwendeten Syntax zu ermöglichen.

host, port = obj{"host", "port"}
first, last = obj{0, -1}

Auch diese Idee ist nicht notwendig, damit Musterabgleich nützlich ist, daher ist sie nicht Teil dieses PEP. Wie beim Abrufen mehrerer Attribute wird sie jedoch als Beispiel dafür aufgenommen, wie die vorgeschlagene Musterabgleichsyntax Ideen zur einfacheren Objekt-Dekonstruktion im Allgemeinen inspirieren kann.

Abgelehnte Ideen

Einschränkung zulässiger Ausdrücke in Wertbeschränkungen und Mapping-Muster-Schlüsseln

Während es technisch durchaus möglich ist, die Arten von Ausdrücken, die in Wertbeschränkungen und Zuordnungsmusterschlüsseln zulässig sind, auf nur Attributsuchen und konstante Literale zu beschränken (wie es PEP 634 tut), gibt es keinen klaren Laufzeitvorteil, dies zu tun. Daher schlägt dieses PEP vor, jede Art von primärem Ausdruck (primäre Ausdrücke sind ein existierender Knotentyp in der Grammatik, der Dinge wie Literale, Namen, Attributsuchen, Funktionsaufrufe, Container-Subskripte, geklammerte Gruppen usw. umfasst) sowie hochpriorisierte unäre Operationen (+, -, ~) auf primäre Ausdrücke zu erlauben.

PEP 635 betont zwar mehrmals, dass Literalmuster und Wertmuster keine vollständigen Ausdrücke sind, aber es wird nie ein konkreter Vorteil genannt, der sich aus dieser Einschränkung ergibt (nur ein theoretischer Appell an die Nützlichkeit, statische Prüfungen von dynamischen Prüfungen zu trennen, was ein Stilprüfungs-Tool immer noch erzwingen könnte, auch wenn der Compiler selbst permissiver ist).

Das letzte Mal, als wir eine solche Einschränkung für Dekorator-Ausdrücke auferlegten, war das Hauptergebnis, dass Benutzer jahrelang mit umständlichen syntaktischen Workarounds leben mussten (wie das Verschachteln beliebiger Ausdrücke in Funktionsaufrufe, die einfach ihr Argument zurückgaben), um das gewünschte Verhalten auszudrücken, bevor die Sprachdefinition schließlich aktualisiert wurde, um beliebige Ausdrücke zuzulassen und den Benutzern ihre eigenen Entscheidungen über die Lesbarkeit zu überlassen.

Die Situation in PEP 634, die eine Ähnlichkeit mit der Situation bei Dekorator-Ausdrücken aufweist, ist, dass beliebige Ausdrücke in Wertmustern technisch unterstützt werden, sie erfordern nur umständliche Workarounds, bei denen entweder alle zu vergleichenden Werte in einer Hilfsklasse angegeben werden müssen, die vor der Abgleichanweisung platziert wird.

# 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()'

Oder sie müssen als Kombination aus Erfassungsmuster und Guard-Ausdruck geschrieben werden.

# Allowing arbitrary match targets with PEP 634's guard expressions
match expr:
    case (_, _matched) if _matched == func():
        ... # Handle the case where 'expr[1] == func()'

Dieses PEP schlägt vor, auf solche Workarounds zu verzichten und stattdessen beliebige Wertbeschränkungen von Anfang an zu unterstützen.

match expr:
    case (__, == func()):
        ... # Handle the case where 'expr == func()'

Ob das tatsächliche Schreiben solcher Art von Code eine gute Idee wäre, wäre ein Thema für Stilrichtlinien und Code-Linters, nicht für den Sprachcompiler.

Insbesondere wenn statische Analysatoren bestimmte Arten von dynamischen Prüfungen nicht nachvollziehen können, können sie die zulässigen Ausdrücke zur Analysezeit einschränken, anstatt dass der Compiler sie zur Kompilierzeit einschränkt.

Es gibt auch einige Arten von Ausdrücken, die aufgrund der Muster-Caching-Regel, bei der die Anzahl der Auswertungen des Constraint-Ausdrucks von der Implementierung abhängt, fast sicher zu unsinnigen Ergebnissen führen (z. B. yield, yield from, await). Selbst hier vertritt das PEP die Ansicht, dass Benutzer Unsinn schreiben dürfen, wenn sie es wirklich wollen.

Abgesehen von den kürzlich aktualisierten Dekorator-Ausdrücken, ist eine weitere Situation, in der Pythons formale Syntax volle Ausdrucksfreiheit bietet, die fast nie praktisch genutzt wird, in except-Klauseln: Die zu vergleichenden Ausnahmen sind fast immer einfache Namen, Punktnamen oder Tupel davon, aber die Sprachgrammatik erlaubt an dieser Stelle beliebige Ausdrücke. Dies ist ein guter Hinweis darauf, dass Pythons Benutzerbasis darauf vertrauen kann, verantwortungsbewusst lesbare Wege zur Nutzung permissiver Sprachfunktionen zu finden, indem sie das Schreiben schwer lesbarer Konstrukte vermeidet, auch wenn sie vom Compiler zugelassen werden.

Diese Permissivität bringt einen echten konkreten Vorteil auf der Implementierungsseite: Dutzende Zeilen von Abgleichanweisungs-spezifischem Code im Compiler werden durch einfache Aufrufe des bestehenden Codes zur Kompilierung von Ausdrücken ersetzt (einschließlich der AST-Validierungsphase, der AST-Optimierungsphase, der Symboltabellenanalysephase und der Codegenerierungsphase). Dieser Implementierungsnutzen käme nicht nur CPython zugute, sondern jeder anderen Python-Implementierung, die Unterstützung für Abgleichsanweisungen hinzufügen möchte.

Erfordert die Verwendung von Präfix-Markern für Mapping-Muster-Schlüssel

Der erste (unveröffentlichte) Entwurf dieses Vorschlags schlug vor, dass Zuordnungsmusterschlüssel Wertbeschränkungen sein müssen, genau wie PEP 634 fordert, dass sie gültige Literal- oder Wertmuster sind.

import constants

match config:
    case {== "route": route}:
        process_route(route)
    case {== constants.DEFAULT_PORT: sub_config, **rest}:
        process_config(sub_config, rest)

Die zusätzlichen Zeichen waren jedoch syntaktisch unruhig und im Gegensatz zur Verwendung in Wertbeschränkungen (wo sie sie von Nicht-Muster-Ausdrücken unterscheiden) liefert das Präfix hier keine zusätzlichen Informationen, die nicht bereits durch den Ausdruck als Schlüssel innerhalb eines Zuordnungsmusters vermittelt werden.

Dementsprechend wurde der Vorschlag vereinfacht, das Markierungspräfix von Zuordnungsmusterschlüsseln wegzulassen.

Diese Auslassung stimmt auch mit der Tatsache überein, dass Container sowohl Identitäts- als auch Gleichheitsprüfungen in ihren Lookup-Prozess einbeziehen können – sie verlassen sich nicht rein auf Gleichheitsprüfungen, wie die Verwendung des Gleichheitsbeschränkungspräfixes fälschlicherweise implizieren würde.

Zulassen, dass der Schlüssel/Wert-Separator für Mapping-Wertbeschränkungen weggelassen werden kann

Instanzattributmuster erlauben die Auslassung des Trennzeichens : beim Schreiben von Attributwertbeschränkungen wie case object{.attr == expr}.

Das Anbieten einer ähnlichen Kurzform für Zuordnungswertbeschränkungen wurde in Erwägung gezogen, aber ihre Zulassung ermöglicht zutiefst verwirrende Konstrukte wie case {0 == 0}:, bei denen der Compiler weiß, dass dies der Schlüssel 0 mit der Wertbeschränkung == 0 ist, aber ein menschlicher Leser die tautologische Vergleichsoperation 0 == 0 sieht. Mit dem Schlüssel/Wert-Trenner ist die Absicht auch für einen menschlichen Leser offensichtlicher: case {0: == 0}:

Referenzimplementierung

Eine Entwurf-Referenzimplementierung für dieses PEP [3] wurde von Brandt Buchers Referenzimplementierung für PEP 634 [4] abgeleitet.

Im Vergleich zum Text dieses PEP hat die Entwurf-Referenzimplementierung die spezielle Behandlung mehrerer integrierter und Standardbibliothekstypen in MATCH_CLASS noch nicht mit der allgemeineren Prüfung auf __match_args__, das auf None gesetzt ist, ergänzt. Von Klassen definierte Muster akzeptieren derzeit auch noch Klassen, die __match_args__ nicht definieren.

Alle anderen geänderten Muster wurden aktualisiert, um diesem PEP anstelle von PEP 634 zu folgen.

Das "Unparsing" für Abgleichmuster wurde noch nicht auf die aktualisierte v3 AST migriert.

Der AST-Validator für Abgleichmuster wurde noch nicht implementiert.

Der AST-Validator im Allgemeinen wurde noch nicht überprüft, um sicherzustellen, dass er prüft, dass nur Ausdrucksknoten übergeben werden, wo Ausdrucksknoten erwartet werden.

Die Beispiele in diesem PEP wurden noch nicht in Testfälle umgewandelt, so dass sie plausibel Tippfehler und andere Fehler enthalten könnten.

Mehrere der alten PEP 634-Tests müssen noch in neue SyntaxError-Tests umgewandelt werden.

Die Dokumentation wurde noch nicht aktualisiert.

Danksagungen

Die Autoren von PEP 622 und PEP 634/PEP 635/PEP 636, da der Vorschlag in diesem PEP lediglich ein Versuch ist, die Lesbarkeit einer bereits gut konstruierten Idee zu verbessern, indem vorgeschlagen wird, dass es eine bessere Option ist, mit einer expliziteren Syntax zu beginnen und potenziell später syntaktische Abkürzungen für besonders gängige Operationen einzuführen, anstatt zu versuchen, *nur* die Abkürzungsversion zu definieren. Für Bereiche der Spezifikation, in denen sich die beiden PEPs gleichen (oder zumindest sehr ähnlich sind), ist der Text, der das beabsichtigte Verhalten in diesem PEP beschreibt, oft direkt vom Text von PEP 634 abgeleitet.

Steven D’Aprano, der einen überzeugenden Fall dafür machte, dass die Kernziele dieses PEP durch die Verwendung bestehender Vergleichstoken erreicht werden könnten, um dem Compiler die Möglichkeit zu geben, die Leistung zu überschreiben, wenn unsere Vermutungen über "was die meisten Benutzer meistens wollen" für einige Benutzer unvermeidlich falsch sind, und einige der syntaktischen Zuckerwürfel von PEP 634 beizubehalten (mit einer leicht anderen semantischen Definition), um die gleiche Kürze wie PEP 634 in den meisten Situationen zu erreichen. (Paul Sokolosvsky schlug auch unabhängig vor, == anstelle von ? als leichter verständliches Präfix für Gleichheitsbeschränkungen zu verwenden).

Thomas Wouters, dessen Veröffentlichung von PEP 640 und öffentliche Überprüfung der Vorschläge für strukturelles Musterabgleichen den Autor dieses PEP überzeugte, sich weiterhin für eine Wildcard-Muster-Syntax einzusetzen, die ein zukünftiges PEP plausibel in ein hartes Schlüsselwort verwandeln könnte, das eine Referenzbindung an jeder Stelle überspringt, an der ein einfacher Name erwartet wird, anstatt unbegrenzt als das Match-Pattern-spezifische Soft-Keyword fortzubestehen, das hier vorgeschlagen wird.

Joao Bueno und Jim Jewett dafür, dass sie den PEP-Autor dazu anregten, sich die vorgeschlagene Syntax für die Erfassung von Unterelementen innerhalb von Klassenmustern und Zuordnungsmustern genauer anzusehen (insbesondere die Probleme mit dem "Rechts-Erfassen"). Diese Überprüfung veranlasste die bedeutenden Änderungen zwischen v2 und v3 des Vorschlags.

Referenzen

Anhang A – Vollständige Grammatik

Hier ist die vollständige geänderte Grammatik für match_stmt, die Anhang A in PEP 634 ersetzt.

Die über die Standard-EBNF hinaus verwendete Notation ist wie in PEP 534

  • 'KWD' bezeichnet ein hartes Schlüsselwort
  • "KWD" bezeichnet ein weiches Schlüsselwort
  • SEP.RULE+ ist eine Abkürzung für RULE (SEP RULE)*
  • !RULE ist eine negative Lookahead-Assertion
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] '}'

Anhang B: Zusammenfassung der Änderungen am Abstract Syntax Tree

Die folgenden neuen Knoten werden von diesem PEP zum AST hinzugefügt

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

Anhang C: Zusammenfassung der Änderungen im Vergleich zu PEP 634

Die allgemeine Syntax der match/case-Anweisung und die Syntax für Guard-Ausdrücke bleiben unverändert, wie sie in PEP 634 sind.

Relativ zu PEP 634 macht dieser PEP die folgenden wichtigen Änderungen

  • ein neuer pattern-Typ wird im AST definiert, anstatt den expr-Typ für Muster wiederzuverwenden
  • die neuen AST-Knoten MatchAs und MatchOr werden vom expr-Typ in den pattern-Typ verschoben
  • das Wildcard-Muster ändert sich von _ (einzelner Unterstrich) zu __ (doppelter Unterstrich) und erhält einen dedizierten MatchAlways-Knoten im AST
  • aufgrund von Mehrdeutigkeiten der Absicht werden Wertemuster und Literalmuster entfernt
  • eine neue Ausdruckskategorie wird eingeführt: „geschlossene Ausdrücke“
  • geschlossene Ausdrücke sind entweder Primärausdrücke oder ein geschlossener Ausdruck, dem einer der monotonen Operatoren höherer Priorität vorangestellt ist (+, -, ~)
  • ein neuer Mustertyp wird eingeführt: „Wertbeschränkungsmuster“
  • Wertbeschränkungen haben einen dedizierten AST-Knoten MatchValue anstatt einer Kombination aus Constant (Literale), UnaryOp (negative Zahlen), BinOp (komplexe Zahlen) und Attribute (Attributsuchen)
  • Wertbeschränkungsmuster sind entweder Gleichheitsbeschränkungen oder Identitätsbeschränkungen
  • Gleichheitsbeschränkungen verwenden == als Präfix-Marker für einen ansonsten beliebigen geschlossenen Ausdruck: == EXPR
  • Identitätsbeschränkungen verwenden is als Präfix-Marker für einen ansonsten beliebigen geschlossenen Ausdruck: is EXPR
  • aufgrund von Mehrdeutigkeiten der Absicht werden Capture-Muster entfernt. Alle Capture-Operationen verwenden das Schlüsselwort as (auch beim Abgleich von Sequenzen) und werden im AST als Knoten MatchAs oder MatchRestOfSequence dargestellt.
  • Um die Ausführlichkeit bei AS-Mustern zu reduzieren, ist as NAME zulässig, mit der gleichen Bedeutung wie __ as NAME
  • Sequenzmuster erfordern jetzt die Verwendung von eckigen Klammern, anstatt die gleiche syntaktische Flexibilität wie bei Zuweisungszielen zu bieten (Zuweisungsanweisungen erlauben das Entpacken von Iterables durch jede Verwendung eines Tupels mit getrenntem Ziel, mit oder ohne umgebende Klammern oder eckige Klammern)
  • Sequenzmuster erhalten einen dedizierten AST-Knoten MatchSequence anstatt List wiederzuverwenden
  • Mapping-Muster werden so geändert, dass beliebige geschlossene Ausdrücke als Schlüssel zulässig sind
  • Mapping-Muster erhalten einen dedizierten AST-Knoten MatchMapping anstatt Dict wiederzuverwenden
  • Um die Ausführlichkeit bei Mapping-Mustern zu reduzieren, darf KEY : __ as NAME zu KEY as NAME gekürzt werden
  • Klassenmuster verwenden nicht mehr die einzelne Schlüsselwortargument-Syntax für Attributabgleiche. Stattdessen verwenden sie die Double-Star-Syntax, zusammen mit einer Variante der Mapping-Muster-Syntax mit einem Punktpräfix bei den Attributnamen
  • Klassenmuster erhalten einen dedizierten AST-Knoten MatchClass anstatt Call wiederzuverwenden
  • Um die Ausführlichkeit zu reduzieren, erlaubt der Abgleich von Klassenattributen das Weglassen von :, wenn das abzugleichende Muster mit ==, is oder as beginnt
  • Klassenmuster behandeln jede Klasse, die __match_args__ auf None setzt, so, als ob sie ein einzelnes Positionsmuster akzeptiert, das gegen das gesamte Objekt abgeglichen wird (wodurch die Sonderbehandlung vermieden wird, die in PEP 634 erforderlich ist)
  • Klassenmuster lösen TypeError aus, wenn sie mit einem Objekt verwendet werden, das __match_args__ nicht definiert
  • dedizierte Syntax für Ducktyping wird hinzugefügt, so dass case cls{...}: grob äquivalent zu case cls(**{...}): ist, aber die Prüfung auf die Existenz von __match_args__ überspringt. Dieses Muster hat ebenfalls einen dedizierten AST-Knoten, MatchAttrs

Beachten Sie, dass das Aufschieben von Literalmustern auch die Frage aufschieben lässt, ob wir einen „INUMBER“-Token im Tokenizer für imaginäre Literale benötigen. Ohne diesen kann der Parser keine komplexen Literale von anderen binären Additions- und Subtraktionsoperationen auf Konstanten unterscheiden, sodass Vorschläge wie PEP 634 in späteren Kompilierungsschritten Arbeiten durchführen müssen, um die korrekte Verwendung zu überprüfen.

Anhang D: Historie der Änderungen an diesem Vorschlag

Die erste veröffentlichte Iteration dieses Vorschlags folgte größtenteils PEP 634, schlug jedoch die Verwendung von ?EXPR für Gleichheitsbeschränkungen und ?is EXPR für Identitätsbeschränkungen anstelle der Wertemuster und Literalmuster von PEP 634 vor.

Die zweite veröffentlichte Iteration übernahm größtenteils einen Gegenvorschlag von Steven D’Aprano, der die von PEP 634 abgeleiteten Einschränkungen in vielen Situationen beibehielt, aber auch die Verwendung von == EXPR für explizite Gleichheitsbeschränkungen und is EXPR für explizite Identitätsbeschränkungen erlaubte.

Die dritte veröffentlichte (und aktuelle) Iteration verwarf die abgeleiteten Muster vollständig, um die Bedenken bezüglich der Tatsache auszuräumen, dass die Muster case {key: NAME}: und case cls(attr=NAME): beide NAME binden würden, obwohl es rechts von einem anderen Unterausdruck erscheint, ohne das Schlüsselwort as zu verwenden. Der überarbeitete Vorschlag eliminiert auch die Möglichkeit, case TARGET1 as TARGET2: zu schreiben, was an beide gegebenen Namen binden würde. Von diesen Änderungen war die besorgniserregendste case cls(attr=TARGET_NAME):, da sie die Verwendung von = mit dem Bindungsziel auf der rechten Seite beinhaltete, genau das Gegenteil dessen, was bei Zuweisungsanweisungen, Funktionsaufrufen und Funktionssignaturdeklarationen geschieht.


Quelle: https://github.com/python/peps/blob/main/peps/pep-0642.rst

Zuletzt geändert: 2025-02-01 08:59:27 GMT