La correspondance de motifs a finalement été apportée à l'anniversaire mineur du troisième python. Le concept lui-même peut difficilement être qualifié de nouveau, il a déjà été implémenté dans de nombreux langages, à la fois de la nouvelle génération (Rust, Golang) et ceux qui dépassent déjà 0x18 (Java).
Le pattern matching a été annoncé par Guido van Rossum , l'auteur du langage de programmation Python et «généreux dictateur de longue date»
Je m'appelle Denis Kaishev et je suis réviseur de code pour le cours de développement Middle Python . Dans cet article, je veux vous dire pourquoi Python a une correspondance de modèle et comment l'utiliser.
D'un point de vue syntaxique, la correspondance de modèles est essentiellement la même que dans un certain nombre d'autres langues:
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
Cela peut sembler compliqué et déroutant, mais en réalité, tout se résume à quelque chose comme ceci:
match some_expression: case pattern_1: ... case pattern_2: ...
Il semble beaucoup plus clair et plus agréable à l'œil.
Les modèles eux-mêmes sont divisés en plusieurs groupes:
- Modèles littéraux;
- Modèles de capture;
- Motif de caractères génériques;
- Modèles de valeurs constantes;
- Modèles de séquence;
- Modèles de mappage;
- Modèles de classe.
Je vais vous parler un peu de chacun d'eux.
Modèles littéraux
Le modèle Literal, comme son nom l'indique, implique la mise en correspondance d'une série de valeurs, à savoir des chaînes, des nombres, des booléens et
Il semble que la
string == 'string'
méthode soit utilisée
__eq__
.
match number:
case 42:
print('answer')
case 43:
print('not answer')
Modèles de capture
Un modèle de capture vous permet de lier une variable avec un nom donné dans le modèle et d'utiliser ce nom dans la portée locale.
match greeting:
case "":
print('Hello my friend')
case name:
print(f'Hello {name}')
Motif générique
S'il y a trop d'options correspondantes, vous pouvez utiliser
_
, qui est une certaine valeur par défaut et correspondra à tous les éléments de la structure
match
match number:
case 42:
print("Its’s forty two")
case _:
print("I don’t know, what it is")
Modèles de valeurs constantes
Lorsque vous utilisez des constantes, vous devez utiliser des noms en pointillés, par exemple des énumérations, sinon le modèle de capture fonctionnera.
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')
Et le résultat attendu ne sera pas le plus évident.
Modèles de séquence
Il vous permet de comparer les listes, les tuples et tout autre objet de
collections.abc.Sequence
, sauf
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')
Désormais, il n'est plus nécessaire d'appeler à chaque fois
len()
pour vérifier le nombre d'éléments dans la liste, puisque la méthode sera appelée
__len__
.
Modèles de mappage
Ce groupe est un peu comme le précédent, seulement ici on fait correspondre des dictionnaires, ou, pour être précis, des objets de type
collections.abc.Mapping
. Ils peuvent être assez bien combinés les uns avec les autres.
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)
Et tout irait bien, mais il y a une fonctionnalité. Ce modèle garantit l'entrée de cette (ces) clé (s) dans le dictionnaire, mais la longueur du dictionnaire n'a pas d'importance. Donc, je trouve des arguments positionnels et un mot-clé args apparaîtra à l'écran .
Modèles de classe
Pour les types de données définis par l'utilisateur, la syntaxe est similaire à l'initialisation d'objet.
Voici à quoi cela ressemblera avec l'exemple des classes de données:
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')
Vous pouvez également utiliser
if
, ou ainsi appelé
guard
. Si la condition est fausse, la correspondance de modèle se poursuit. Il est à noter que le modèle correspond en premier, et seulement après cela, la condition est vérifiée:
case Coordinate(x, y, z) if z == 0:
print('Point in the plane XY')
Si vous utilisez directement des classes, alors vous avez besoin d'un attribut
__match_args__
dans lequel des arguments de position sont nécessaires (pour namedtuple et dataclasses, il est
__match_args__
généré automatiquement).
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')
Sinon, une exception TypeError sera levée: Coordinate () accepte 0 sous-motifs positionnels (3 donnés)
Quelle est la ligne de fond?
En fait, il ressemble à un autre sucre syntaxique avec le sucre récent
walrus operator
. L'implémentation , telle qu'elle est, convertit les blocs d'instructions
match
en constructions équivalentes
if/else
, à savoir le bytecode, qui a le même effet.
Armin Ronacher, le créateur du framework web Flask pour Python, très succinctement décrit le . État actuel de la mise en correspondance de motif
Oui, il est difficile d'argumenter: le code deviendra un peu plus propre que ce serait
if/else
un tiers de l'écran tour . Mais vous ne pouvez pas non plus appeler cela quelque chose qui produit un effet wow. Ce n'est pas mal qu'il soit introduit: il sera pratique de l'utiliser dans certains endroits, mais pas partout. D'une manière ou d'une autre, l'essentiel avec cette nouveauté est de ne pas en faire trop, de ne pas courir plus vite pour mettre à jour tous les projets vers la 3.10 et tout réécrire, car:
C'est mieux que jamais. Bien que jamais ne soit souvent mieux que maintenant.
L'utiliserez-vous? Si oui, où?