L'instruction match en Python
Apprenez le filtrage structurel par motif en Python avec match/case : littéraux, séquences, mappages, motifs de classe, gardes et joker — avec des exemples.
Python 3.10 a introduit le filtrage structurel par motif via l'instruction match — un moyen puissant de brancher l'exécution selon la forme et le contenu des données, et pas seulement selon l'égalité. Ce chapitre couvre tout, de la syntaxe de base match/case aux motifs avancés tels que le dépaquetage de séquences, les motifs de mappage, les motifs de classe, les gardes et les cas d'utilisation réels.
Avant de lire ce chapitre, vous devriez être à l'aise avec if/else en Python, les fonctions Python et les structures de données de base (listes, tuples, dictionnaires).
Qu'est-ce que le filtrage structurel par motif ?
Le filtrage structurel par motif vous permet d'inspecter la structure d'un objet — son type, les valeurs de ses champs, la forme d'une séquence — et d'exécuter du code différent selon le motif qui correspond. Cela va bien au-delà d'une simple vérification if x == y.
Prenons l'exemple du routage d'un code de statut HTTP. Avec des chaînes if/elif, on écrit :
if status == 200:
print("OK")
elif status == 404:
print("Not Found")
elif status == 500:
print("Internal Server Error")
else:
print("Unknown status")Avec match, l'intention est plus lisible :
match status:
case 200:
print("OK")
case 404:
print("Not Found")
case 500:
print("Internal Server Error")
case _:
print("Unknown status")Le véritable avantage apparaît lorsque le sujet est un objet complexe — un tuple, un dictionnaire ou une dataclass — et que vous souhaitez le déstructurer lors de la correspondance.
Syntaxe de base
match subject:
case pattern1:
# runs if subject matches pattern1
case pattern2:
# runs if subject matches pattern2
case _:
# wildcard — runs if nothing else matchedRègles à retenir :
matchetcasesont des mots-clés souples — ils ne sont des mots-clés que dans ce contexte et peuvent toujours être utilisés comme noms de variables ailleurs dans votre code.- Chaque bloc
caseest essayé dans l'ordre ; la première correspondance l'emporte et les autres sont ignorées. - Le bloc
case _:est le joker — il correspond toujours et fait office de valeur par défaut universelle. - Python 3.10+ est requis. L'exécution sur Python 3.9 ou antérieur lève une
SyntaxError.
Motifs littéraux
Le motif le plus simple correspond à une valeur concrète : un nombre, une chaîne, True, False ou None.
def http_status(status):
match status:
case 200:
return "OK"
case 404:
return "Not Found"
case 500:
return "Internal Server Error"
case _:
return "Unknown status"
print(http_status(200)) # OK
print(http_status(404)) # Not Found
print(http_status(999)) # Unknown statusMotifs OU (|)
Utilisez | dans un case pour correspondre à l'un de plusieurs littéraux :
def is_vowel(letter):
match letter.lower():
case "a" | "e" | "i" | "o" | "u":
return True
case _:
return False
print(is_vowel("a")) # True
print(is_vowel("b")) # False
print(is_vowel("E")) # TrueLes motifs OU fonctionnent également avec des nombres, None et d'autres types littéraux.
Motifs de capture
Un motif de capture est un nom nu (ni un littéral de chaîne, ni un nom pointé) qui correspond à n'importe quoi et lie la valeur correspondante à ce nom pour l'utiliser dans le corps :
def greet(name):
match name:
case "Alice":
return "Hello, Alice!"
case other: # captures whatever was passed
return f"Hello, {other}!"
print(greet("Alice")) # Hello, Alice!
print(greet("Bob")) # Hello, Bob!other ci-dessus est un motif de capture — il lie la valeur correspondante à la variable locale other. Cela ressemble beaucoup au joker _, mais _ ignore la valeur tandis qu'une capture nommée la conserve.
A bare name in a case is always a capture, never a comparison. If you want to compare against a constant defined elsewhere, use a dotted name like Status.OK or wrap it in a guard (case x if x == my_constant:).
Motifs de séquence
Un motif de séquence correspond aux listes, tuples ou toute séquence, et peut déstructurer les éléments en variables simultanément.
def process_point(point):
match point:
case (0, 0):
return "Origin"
case (x, 0):
return f"On x-axis at {x}"
case (0, y):
return f"On y-axis at {y}"
case (x, y):
return f"Point at ({x}, {y})"
print(process_point((0, 0))) # Origin
print(process_point((5, 0))) # On x-axis at 5
print(process_point((0, 3))) # On y-axis at 3
print(process_point((2, 4))) # Point at (2, 4)Utiliser * pour capturer le reste
Un *name dans un motif de séquence collecte les éléments restants, tout comme le dépaquetage itérable :
def describe_list(items):
match items:
case []:
return "empty list"
case [single]:
return f"one item: {single}"
case [first, *rest]:
return f"starts with {first!r}, then {len(rest)} more item(s)"
print(describe_list([])) # empty list
print(describe_list([42])) # one item: 42
print(describe_list([1, 2, 3, 4])) # starts with 1, then 3 more item(s)Utilisez [first, *_] si vous souhaitez capturer uniquement le premier élément et ignorer le reste.
Motifs de mappage
Un motif de mappage correspond aux dictionnaires (ou à tout Mapping). Vous ne spécifiez que les clés qui vous intéressent — les clés supplémentaires dans le sujet sont ignorées.
def process_event(event):
match event:
case {"type": "click", "button": button}:
return f"Mouse click: button {button}"
case {"type": "keypress", "key": key}:
return f"Key pressed: {key!r}"
case {"type": action}:
return f"Other event: {action}"
case _:
return "Unknown event"
print(process_event({"type": "click", "button": 1}))
# Mouse click: button 1
print(process_event({"type": "keypress", "key": "Enter"}))
# Key pressed: 'Enter'
print(process_event({"type": "resize", "width": 800}))
# Other event: resize
print(process_event({}))
# Unknown eventPoint clé : un motif de mappage n'échoue jamais à cause de clés supplémentaires dans le sujet. {"type": "click", "button": button} correspond même si l'événement contient également des coordonnées "x" et "y".
Pour capturer les paires clé/valeur restantes, utilisez **rest :
match event:
case {"type": "click", **rest}:
print(f"Click event with extra data: {rest}")Motifs de classe
Un motif de classe correspond à une instance d'une classe spécifique et extrait ses attributs. C'est particulièrement utile avec les dataclasses car leurs attributs sont exposés par nom automatiquement.
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
@dataclass
class Circle:
center: Point
radius: float
def describe_shape(shape):
match shape:
case Point(x=0, y=0):
return "Point at origin"
case Point(x=x, y=y):
return f"Point at ({x}, {y})"
case Circle(center=Point(x=cx, y=cy), radius=r):
return f"Circle centered at ({cx}, {cy}) with radius {r}"
case _:
return "Unknown shape"
print(describe_shape(Point(0, 0))) # Point at origin
print(describe_shape(Point(3, 4))) # Point at (3, 4)
print(describe_shape(Circle(Point(1, 2), 5)))# Circle centered at (1, 2) with radius 5Notez le motif de classe imbriqué dans le cas Circle : Point(x=cx, y=cy) est mis en correspondance à l'intérieur du motif Circle. Les motifs peuvent être composés à une profondeur arbitraire.
Pour les types intégrés comme int, str, float et bool, vous pouvez utiliser des motifs positionnels avec un seul argument :
def handle_input(value):
match value:
case (int() | float()) as number:
return f"Got a number: {number}"
case str() as text:
return f"Got text: {text!r}"
case _:
return "Unknown type"
print(handle_input(3.14)) # Got a number: 3.14
print(handle_input("hello")) # Got text: 'hello'
print(handle_input([1, 2])) # Unknown typeLe mot-clé as (le motif AS) lie l'intégralité de la valeur correspondante à un nom, même après une vérification de type.
Gardes
Une garde est une condition if ajoutée après un motif. Le case ne correspond que lorsque le motif correspond et que la garde est évaluée à True.
def classify_number(n):
match n:
case 0:
return "zero"
case x if x < 0:
return f"{x} is negative"
case x if x % 2 == 0:
return f"{x} is positive and even"
case x:
return f"{x} is positive and odd"
print(classify_number(0)) # zero
print(classify_number(-5)) # -5 is negative
print(classify_number(4)) # 4 is positive and even
print(classify_number(7)) # 7 is positive and oddLes gardes sont évaluées après que le motif structurel correspond, donc les variables capturées sont disponibles à l'intérieur. Une garde qui échoue n'empêche pas les cas suivants d'être essayés.
Le motif joker _
_ est le fourre-tout universel. Il correspond à n'importe quelle valeur et ne lie rien (la valeur est ignorée). Il devrait être le dernier case d'un bloc match. Sans lui, un match qui ne trouve aucun case correspondant ne fait simplement rien — aucune erreur n'est levée.
def describe(value):
match value:
case 0:
return "zero"
case _:
return f"something else: {value!r}"
print(describe(0)) # zero
print(describe(99)) # something else: 99
print(describe("hi")) # something else: 'hi'_ peut également apparaître à l'intérieur d'un motif pour ignorer des parties spécifiques :
match point:
case (_, 0):
print("On the x-axis (x value doesn't matter)")
case (0, _):
print("On the y-axis (y value doesn't matter)")Combiner les motifs : un exemple concret
Les motifs ci-dessus se composent. Voici un analyseur de commandes pour un jeu d'aventure textuel qui combine des motifs de séquence, des gardes et le joker :
def run_command(command):
match command.split():
case ["quit"]:
return "Quitting"
case ["go", direction] if direction in ("north", "south", "east", "west"):
return f"Going {direction}"
case ["go", direction]:
return f"Cannot go {direction!r} — try north, south, east, or west"
case ["get", item]:
return f"Picking up {item}"
case ["drop", item]:
return f"Dropping {item}"
case ["inventory"]:
return "Checking inventory"
case [verb, *args]:
return f"Unknown command {verb!r} with args {args}"
case []:
return "No command entered"
print(run_command("go north")) # Going north
print(run_command("go up")) # Cannot go 'up' — try north, south, east, or west
print(run_command("get sword")) # Picking up sword
print(run_command("drop torch")) # Dropping torch
print(run_command("quit")) # Quitting
print(run_command("")) # No command enteredEn lisant ce code de haut en bas, vous comprenez immédiatement chaque commande prise en charge — ce qui nécessiterait bien plus de lignes de logique if/elif pour atteindre la même clarté.
match vs. if/elif — Quand utiliser lequel ?
| Scénario | Meilleur choix |
|---|---|
| Égalité simple contre quelques constantes | L'un ou l'autre ; match est légèrement plus lisible |
| Correspondance sur une structure / forme de données | match — bien plus lisible |
| Déstructuration de valeurs lors de la correspondance | match — impossible avec if |
| Logique impliquant uniquement des conditions calculées | if/elif |
| Python 3.9 ou antérieur | if/elif (match non disponible) |
| Exprimer clairement une table de décision | match |
match ne remplace pas toutes les chaînes if. Lorsque toutes les branches vérifient des conditions booléennes calculées (par exemple, if x > 10 and y < 5), une chaîne if/elif est naturelle. match brille lorsque la condition porte sur la forme des données.
Pièges courants
Les noms de constantes ne sont pas comparés par valeur
Un nom nu dans un case est toujours une capture, jamais une recherche :
STATUS_OK = 200
match response_code:
case STATUS_OK: # WRONG — this captures into STATUS_OK, not compares!
print("Success")Pour comparer avec une constante nommée, utilisez un nom pointé (http.HTTPStatus.OK) ou une garde :
match response_code:
case x if x == STATUS_OK:
print("Success")match n'est pas exhaustif par défaut
Contrairement à switch dans certains autres langages, un match sans case correspondant ne fait silencieusement rien. Ajoutez un case _: si vous avez besoin d'un gestionnaire garanti.
match requiert Python 3.10+
L'exécution d'un bloc match sur Python 3.9 ou antérieur lève SyntaxError: invalid syntax. Vérifiez votre version avec python3 --version. Consultez le guide de démarrage Python si vous devez configurer un environnement Python moderne.
Les motifs ne sont pas des expressions booléennes
Vous ne pouvez pas écrire case x > 5: — c'est une garde, pas un motif. La partie structurelle (case x) doit venir en premier, suivie d'une expression de garde if optionnelle.
Résumé
| Type de motif | Exemple de syntaxe | Ce qu'il correspond |
|---|---|---|
| Littéral | case 42: | Valeur exacte |
| OU | case "yes" | "y": | L'une des alternatives |
| Joker | case _: | N'importe quoi (ignore la valeur) |
| Capture | case x: | N'importe quoi, lie à x |
| Séquence | case [a, b, *rest]: | Une séquence d'au moins 2 éléments |
| Mappage | case {"key": val}: | Un dict contenant les clés données |
| Classe | case Point(x=0, y=y): | Une instance avec des attributs correspondants |
| AS | case int() as n: | Correspond et lie l'intégralité de la valeur |
| Garde | case x if x > 0: | Motif + condition booléenne supplémentaire |
Pratique
Maintenant que vous savez comment brancher l'exécution sur la structure des données, explorez les boucles for Python pour itérer sur des séquences, ou les enum Python pour définir le type de constantes typées qui fonctionnent bien avec les motifs de classe.