Mustervergleich. Jetzt in Python

Hallo!



Pattern Matching wurde endlich zum Jubiläums-Moll der dritten Python gebracht. Das Konzept selbst kann kaum als neu bezeichnet werden, es wurde bereits in vielen Sprachen implementiert, sowohl der neuen Generation (Rust, Golang) als auch derjenigen, die bereits über 0x18 (Java) sind.





Der Mustervergleich wurde von Guido van Rossum , dem Autor der Programmiersprache Python und „großzügigen lebenslangen Diktator“,



angekündigt . Mein Name ist Denis Kaishev und ich bin ein Code-Reviewer für den Middle Python-Entwicklerkurs . In diesem Beitrag möchte ich Ihnen erklären, warum Python über Mustervergleich verfügt und wie Sie damit arbeiten.



Syntaktisch ist der Mustervergleich im Wesentlichen derselbe wie in einer Reihe anderer Sprachen:



match_expr:
    | star_named_expression ',' star_named_expressions?
    | named_expression
match_stmt: "match" match_expr ':' NEWLINE INDENT case_block+ DEDENT
case_block: "case" patterns [guard] ':' block
guard: 'if' named_expression
patterns: value_pattern ',' [values_pattern] | pattern
pattern: walrus_pattern | or_pattern
walrus_pattern: NAME ':=' or_pattern
or_pattern: '|'.closed_pattern+
closed_pattern:
    | capture_pattern
    | literal_pattern
    | constant_pattern
    | group_pattern
    | sequence_pattern
    | mapping_pattern
    | class_pattern
capture_pattern: NAME !('.' | '(' | '=')
literal_pattern:
    | signed_number !('+' | '-')
    | signed_number '+' NUMBER
    | signed_number '-' NUMBER
    | strings
    | 'None'
    | 'True'
    | 'False'
constant_pattern: attr !('.' | '(' | '=')
group_pattern: '(' patterns ')'
sequence_pattern: '[' [values_pattern] ']' | '(' ')'
mapping_pattern: '{' items_pattern? '}'
class_pattern:
    | name_or_attr '(' ')'
    | name_or_attr '(' ','.pattern+ ','? ')'
    | name_or_attr '(' ','.keyword_pattern+ ','? ')'
    | name_or_attr '(' ','.pattern+ ',' ','.keyword_pattern+ ','? ')'
signed_number: NUMBER | '-' NUMBER
attr: name_or_attr '.' NAME
name_or_attr: attr | NAME
values_pattern: ','.value_pattern+ ','?
items_pattern: ','.key_value_pattern+ ','?
keyword_pattern: NAME '=' or_pattern
value_pattern: '*' capture_pattern | pattern
key_value_pattern:
    | (literal_pattern | constant_pattern) ':' or_pattern
    | '**' capture_pattern

      
      





Es mag kompliziert und verwirrend erscheinen, aber in Wirklichkeit läuft alles auf so etwas hinaus:



match some_expression:
    case pattern_1:
        ...
    case pattern_2:
        ...

      
      





Es sieht für das Auge viel klarer und angenehmer aus.



Die Vorlagen selbst sind in mehrere Gruppen unterteilt:



  • Wörtliche Muster;
  • Muster erfassen;
  • Platzhaltermuster;
  • Konstante Wertmuster;
  • Sequenzmuster;
  • Zuordnungsmuster;
  • Klassenmuster.


Ich werde Ihnen ein wenig über jeden von ihnen erzählen.



Wörtliche Muster



Das Literalmuster beinhaltet, wie der Name schon sagt, das Abgleichen einer Reihe von Werten, nämlich Zeichenfolgen, Zahlen, Booleschen Werten und NULL None.



Es sieht so aus, als würde die string == 'string'



Methode verwendet __eq__



.



match number:
    case 42:
        print('answer')
    case 43:
        print('not answer')

      
      





Muster erfassen



Mit einer Erfassungsvorlage können Sie eine Variable mit einem in der Vorlage angegebenen Namen binden und diesen Namen im lokalen Bereich verwenden.



match greeting:
    case "":
        print('Hello my friend')
    case name:
        print(f'Hello  {name}')
      
      







Platzhaltermuster



Wenn zu viele Übereinstimmungsoptionen vorhanden sind, können Sie diese verwenden _



. Dies ist ein bestimmter Standardwert, der mit allen Elementen in der Struktur übereinstimmt match






match number:
    case 42:
        print("Its’s forty two")
    case _:
        print("I don’t know, what it is")
      
      





Konstante Wertmuster



Wenn Sie Konstanten verwenden, müssen Sie gepunktete Namen verwenden, z. B. Aufzählungen. Andernfalls funktioniert das Erfassungsmuster.



OK = 200
CONFLICT = 409

response = {'status': 409, 'msg': 'database error'}
match response['status'], response['msg']:
    case OK, ok_msg:
        print('handler 200')
    case CONFLICT, err_msg:
        print('handler 409')
    case _:
        print('idk this status')
      
      





Und das erwartete Ergebnis wird nicht das offensichtlichste sein.



Sequenzmuster



Es ermöglicht Ihnen , Listen, Tupel zu vergleichen, und alle anderen Objekte aus collections.abc.Sequence



, mit der Ausnahme str



, bytes



, bytearray



.



answer = [42]
match answer:
    case []:   
        print('i do not find answer')
    case [x]:
        print('asnwer is 42')
    case [x, *_]:
        print('i find more than one answers')
      
      





Jetzt müssen Sie nicht mehr jedes Mal aufrufen len()



, um die Anzahl der Elemente in der Liste zu überprüfen, da die Methode aufgerufen wird __len__



.



Zuordnungsmuster



Diese Gruppe ist ein bisschen wie die vorherige, nur dass wir hier Wörterbücher oder genauer gesagt Objekte vom Typ abgleichen collections.abc.Mapping



. Sie können sehr gut miteinander kombiniert werden.



args = (1, 2)
kwargs = {'kwarg': 'kwarg', 'one_more_kwarg': 'one_more_kwarg'}

def match_something(*args, **kwargs):
    match (args, kwargs):
        case (arg1, arg2), {'kwarg': kwarg}:
            print('i find positional args and one keyword args')
        case (arg1, arg2), {'kwarg': kwarg, 'one_more_kwarg': one_more_kwarg}:
            print('i find a few keyword args')
        case _:
            print('i cannot match anything')

match_something(*args, **kwargs)
      
      





Und alles wäre in Ordnung, aber es gibt eine Funktion. Dieses Muster garantiert die Eingabe dieser Schlüssel in das Wörterbuch, aber die Länge des Wörterbuchs spielt keine Rolle. Ich finde also Positionsargumente und ein Schlüsselwortargument wird auf dem Bildschirm angezeigt .



Klassenmuster



Bei benutzerdefinierten Datentypen ähnelt die Syntax der Objektinitialisierung.



So sieht es am Beispiel von Datenklassen aus:



from dataclasses import dataclass

@dataclass
class Coordinate:
    x: int
    y: int
    z: int

coordinate = Coordinate(1, 2, 3)
match coordinate:
    case Coordinate(0, 0, 0):
        print('Zero point')
    case _:
        print('Another point')

      
      





Sie können auch if



oder so genannt verwenden guard



. Wenn die Bedingung falsch ist, wird der Mustervergleich fortgesetzt. Es ist erwähnenswert, dass das Muster zuerst abgeglichen wird und erst danach die Bedingung überprüft wird:



case Coordinate(x, y, z) if z == 0:
    print('Point in the plane XY')
      
      





Wenn Sie Klassen direkt verwenden, benötigen Sie ein Attribut, __match_args__



in dem Positionsargumente benötigt werden (für Namedtuple und Datenklassen wird es __match_args__



automatisch generiert).



class Coordinate:
    __match_args__ = ['x', 'y', 'z']

    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z

oordinate = oordinate(1, 2, 3)
match oordinate:
    case oordinate(0, 0, 0):
        print('Zero oordinate')
    case oordinate(x, y, z) if z == 0:
        print('oordinate in the plane Z')
    case _:
        print('Another oordinate')

      
      





Andernfalls wird eine TypeError-Ausnahme ausgelöst: Coordinate () akzeptiert 0 Positionsuntermuster (3 angegeben)



Was ist das Endergebnis?



Tatsächlich sieht es aus wie ein anderer syntaktischer Zucker zusammen mit dem jüngsten walrus operator



. Die Implementierung konvertiert derzeit Anweisungsblöcke match



in äquivalente Konstrukte if/else



, nämlich Bytecode, was den gleichen Effekt hat.





Armin Ronacher, der Schöpfer des Flask-Webframeworks für Python, hat den aktuellen Status des Pattern Matching sehr kurz beschrieben .



Ja, es ist schwer zu argumentieren: Der Code wird etwas sauberer als if/else



ein Drittel des Bildschirmturms. Aber Sie können es auch nicht als etwas bezeichnen, das einen Wow-Effekt erzeugt. Es ist nicht schlecht, dass es eingeführt wird: Es wird praktisch sein, es an einigen Orten zu verwenden, aber nicht überall. Auf die eine oder andere Weise ist die Hauptsache bei dieser Neuheit, sie nicht zu übertreiben, nicht schneller zu laufen, um alle Projekte auf 3.10 zu aktualisieren und alles neu zu schreiben, weil:

Jetzt ist besser als nie. Obwohl nie oft besser ist als jetzt.


Wirst du es benutzen? Wenn ja, wo?



All Articles