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