Le mécanisme de correspondance de motifs est apparu dans Python 3.10 sous le nom de Structural Pattern Matching et est implémenté à l'aide de la construction match-case. Cet outil permet d'analyser élégamment et de manière concise des structures de données complexes en les comparant à des modèles.
Dans de nombreux langages fonctionnels (par exemple, Haskell, Scala), la correspondance de motifs est depuis longtemps considérée comme une méthode pratique pour brancher la logique en fonction de la structure des données. Python a longtemps manqué de ce mécanisme, se contentant de chaînes if-elif-else ou de déballage.
Les structures de données complexes imbriquées (dictionnaires, listes, objets) nécessitent souvent de multiples vérifications des types, des valeurs et de la structure, ce qui entraîne un code confus avec de nombreuses conditions.
match-case offre un moyen déclaratif et lisible de décrire divers scénarios de traitement des données en tenant compte du type et de la structure, sans encombrement de conditions.
def process(event): match event: case {"type": "click", "x": x, "y": y}: return f"Click at ({x}, {y})" case [command, *args]: return f"Command: {command}, args: {args}" case _: return "Unknown event" print(process({"type": "click", "x": 2, "y": 5})) # Click at (2, 5) print(process(["RUN", 1, 2, 3])) # Command: RUN, args: [1, 2, 3]
La correspondance match-case ne s'effectue-t-elle que par valeur ? Non. La correspondance peut se faire par structure, type, déballage de collections et même par propriétés d'objets (si l'on spécifie des méthodes spéciales match_args).
Le ordre des cas dans match a-t-il un impact ? Oui. L'analyse se fait de haut en bas, le premier modèle correspondant s'exécute. Les cas suivants ne sont pas vérifiés.
Peut-on faire correspondre des classes personnalisées ? Oui, si la classe implémente les méthodes match_args et/ou getitem. Alors la correspondance de motifs pourra extraire les valeurs des champs.
class Point: __match_args__ = ("x", "y") def __init__(self, x, y): self.x = x self.y = y def where(obj): match obj: case Point(x, y): return f"Point at {x}, {y}" case _: return "Unknown" print(where(Point(1, 2))) # Point at 1, 2
Avantages :
Cas négatif : Les développeurs ont utilisé match-case même pour de simples branches booléennes, ce qui a compliqué le code. Avantages : ils ont appris une nouvelle fonctionnalité. Inconvénients : ils ont perdu en lisibilité et augmenté le nombre d'erreurs.
Cas positif : Dans une application avec un grand nombre d'événements et de structures imbriquées (par exemple, un analyseur de graphes), match-case a permis d'éviter de lourds if-elif et de centraliser l'analyse. Avantages : la lisibilité a augmenté, le nombre de bogues a diminué. Inconvénients : il a fallu former l'équipe à la nouvelle construction.