Aller au contenu
Développement medium

Python match case : le pattern matching structurel

14 min de lecture

logo python

L'instruction match/case compare une valeur à une série de motifs (patterns) et exécute le premier bloc qui correspond. Introduite par Python 3.10 (PEP 634), elle ne se limite pas à tester une égalité comme le switch d'autres langages : elle déstructure la valeur, c'est-à-dire qu'elle inspecte sa forme (une liste de deux éléments, un dictionnaire avec telle clé, une instance de classe) et extrait les morceaux qui vous intéressent au passage. C'est le structural pattern matching. Ce guide couvre les motifs littéraux, la capture de variable, le wildcard _, les motifs de séquence et de mapping, les motifs de classe, l'opérateur | et les gardes.

Il s'adresse aux développeurs qui écrivent des cascades de if/elif à rallonge et cherchent une écriture plus lisible pour aiguiller sur la structure d'une donnée. Les exemples ont été exécutés avec Python 3.12.

  • Écrire une instruction match/case de base avec le wildcard _.
  • Différencier un motif littéral d'un motif de capture.
  • Déstructurer des séquences et des dictionnaires directement dans le case.
  • Filtrer sur le type et les attributs d'un objet avec les motifs de classe.
  • Combiner des motifs avec | et affiner avec une garde if.
  • Savoir quand match est plus clair qu'une cascade de if/elif.

Une instruction match prend une valeur, puis la confronte à chaque case dans l'ordre. Le premier motif qui correspond gagne, et Python s'arrête là (pas de fallthrough, donc aucun break à écrire). Le motif spécial _, appelé wildcard, correspond à tout : il joue le rôle du cas par défaut.

def decrire_feu(couleur):
match couleur:
case "vert":
return "Passez"
case "orange":
return "Ralentissez"
case "rouge":
return "Arretez-vous"
case _:
return "Couleur inconnue"
for c in ["vert", "orange", "rouge", "bleu"]:
print(c, "->", decrire_feu(c))

La sortie confirme que chaque couleur connue tombe sur son case, et que "bleu" termine sur le wildcard :

vert -> Passez
orange -> Ralentissez
rouge -> Arretez-vous
bleu -> Couleur inconnue

Ici, les motifs "vert", "orange" et "rouge" sont des motifs littéraux : ils testent une égalité avec une constante. C'est le cas le plus simple, très proche d'un switch classique.

Un motif de capture n'est pas une constante, mais un nom de variable. Il correspond toujours, et lie la valeur testée à ce nom pour que vous puissiez la réutiliser dans le bloc. C'est la grande différence avec un switch : le case ne fait pas que comparer, il extrait.

def message_statut(code):
match code:
case 200:
return "OK"
case 404:
return "Page introuvable"
case autre:
return f"Code non gere : {autre}"
print(message_statut(200))
print(message_statut(404))
print(message_statut(500))

Le troisième case autre attrape tout ce qui n'est ni 200 ni 404, et récupère la valeur dans autre :

OK
Page introuvable
Code non gere : 500

Un motif de séquence décrit la forme d'une liste ou d'un tuple : combien d'éléments, et comment les nommer. On l'écrit avec des crochets, comme une liste, mais chaque position est un sous-motif. L'étoile *reste capture « tout le reste » de la séquence, exactement comme le unpacking que vous connaissez déjà.

def analyser_commande(commande):
match commande:
case []:
return "Aucune commande"
case [action]:
return f"Action seule : {action}"
case [action, cible]:
return f"{action} sur {cible}"
case [action, *arguments]:
return f"{action} avec {len(arguments)} arguments : {arguments}"
print(analyser_commande([]))
print(analyser_commande(["lister"]))
print(analyser_commande(["copier", "rapport.pdf"]))
print(analyser_commande(["deployer", "web", "api", "cache"]))

Chaque motif teste une longueur différente, et le dernier absorbe les arguments restants :

Aucune commande
Action seule : lister
copier sur rapport.pdf
deployer avec 3 arguments : ['web', 'api', 'cache']

Ce mécanisme brille pour router une commande ou décomposer une ligne déjà découpée. Notez que le motif teste la structure (le nombre d'éléments), pas seulement la valeur : c'est ce qui dépasse largement un switch.

Un motif de mapping inspecte un dictionnaire par ses clés. Vous écrivez les clés qui vous intéressent et un sous-motif pour chaque valeur associée. Point important : le motif correspond même si le dictionnaire contient d'autres clés en plus. Il vérifie une présence, pas une égalité exacte.

def traiter_evenement(evenement):
match evenement:
case {"type": "connexion", "utilisateur": nom}:
return f"Connexion de {nom}"
case {"type": "erreur", "code": code}:
return f"Erreur {code}"
case {"type": type_inconnu}:
return f"Evenement non gere : {type_inconnu}"
print(traiter_evenement({"type": "connexion", "utilisateur": "Alice"}))
print(traiter_evenement({"type": "erreur", "code": 500, "detail": "timeout"}))
print(traiter_evenement({"type": "deconnexion"}))

Le deuxième appel correspond bien au motif erreur alors qu'il porte une clé detail supplémentaire, ignorée sans erreur :

Connexion de Alice
Erreur 500
Evenement non gere : deconnexion

Ce motif est taillé pour les charges JSON et les événements typés par une clé "type". Vous testez la clé discriminante et capturez les champs utiles en une seule ligne, sans empiler des accès par index.

Un motif de classe vérifie qu'une valeur est une instance d'une classe, tout en filtrant sur ses attributs. On écrit le nom de la classe suivi des attributs entre parenthèses, comme un appel de constructeur inversé. C'est particulièrement lisible avec une dataclass, dont les attributs sont explicites.

from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
def situer(point):
match point:
case Point(x=0, y=0):
return "Origine"
case Point(x=0, y=y):
return f"Sur l'axe vertical a y={y}"
case Point(x=x, y=0):
return f"Sur l'axe horizontal a x={x}"
case Point(x=x, y=y):
return f"Point libre ({x}, {y})"
print(situer(Point(0, 0)))
print(situer(Point(0, 5)))
print(situer(Point(3, 0)))
print(situer(Point(2, 7)))

Chaque case combine une contrainte (x=0, motif littéral) et une capture (y=y, motif de capture) sur les attributs :

Origine
Sur l'axe vertical a y=5
Sur l'axe horizontal a x=3
Point libre (2, 7)

Ce motif s'appuie sur la programmation orientée objet : il faut une classe pour l'utiliser. Si les objets et les attributs sont encore flous, la POO en Python pose les bases nécessaires avant d'aller plus loin.

L'opérateur | (le OU, ou or-pattern) regroupe plusieurs motifs dans un même case : il correspond si l'un d'eux correspond. Cela évite de dupliquer un bloc identique pour des valeurs qui déclenchent le même traitement.

def categorie_jour(jour):
match jour:
case "samedi" | "dimanche":
return "Week-end"
case "lundi" | "mardi" | "mercredi" | "jeudi" | "vendredi":
return "Jour ouvre"
case _:
return "Jour invalide"
print(categorie_jour("samedi"))
print(categorie_jour("mardi"))
print(categorie_jour("nimportequoi"))

Les deux jours du week-end partagent un seul bloc, tout comme les cinq jours ouvrés :

Week-end
Jour ouvre
Jour invalide

Le | reste lisible tant que la liste ne s'allonge pas trop. Au-delà d'une poignée de valeurs, un simple test d'appartenance if jour in {...} redevient plus direct.

Une garde est une condition if ajoutée après un motif. Le case ne correspond que si le motif et la condition sont vrais. La garde permet de tester ce qu'un motif seul ne peut pas exprimer : une comparaison (< 15), une relation entre variables (x == y), un appel de fonction.

def evaluer_temperature(mesure):
match mesure:
case temp if temp < 0:
return f"{temp} degres : gel"
case temp if temp < 15:
return f"{temp} degres : froid"
case temp if temp < 30:
return f"{temp} degres : tempere"
case temp:
return f"{temp} degres : chaud"
print(evaluer_temperature(-5))
print(evaluer_temperature(10))
print(evaluer_temperature(22))
print(evaluer_temperature(35))

Chaque case capture la valeur dans temp, puis la garde décide de l'intervalle :

-5 degres : gel
10 degres : froid
22 degres : tempere
35 degres : chaud

La garde se marie très bien avec un motif de classe pour tester une relation entre attributs, impossible à écrire dans le motif lui-même :

def livraison(point):
match point:
case Point(x=x, y=y) if x == y:
return f"Sur la diagonale ({x}, {y})"
case Point(x=x, y=y):
return f"Hors diagonale ({x}, {y})"
print(livraison(Point(4, 4)))
print(livraison(Point(4, 9)))
Sur la diagonale (4, 4)
Hors diagonale (4, 9)

match n'est pas un remplacement systématique de if/elif. Pour un test booléen simple ou une seule condition, un if reste plus court et plus clair. Le pattern matching gagne quand vous aiguillez sur la structure d'une donnée : une forme de liste, la présence de clés dans un dictionnaire, le type et les attributs d'un objet.

Comparez ces deux versions du routage de commande. En if/elif, la logique de longueur et d'extraction se répète à la main :

def analyser_if(commande):
if len(commande) == 0:
return "Aucune commande"
elif len(commande) == 1:
return f"Action seule : {commande[0]}"
elif len(commande) == 2:
return f"{commande[0]} sur {commande[1]}"
else:
return f"{commande[0]} avec {len(commande) - 1} arguments"

La version match déclare la forme attendue et nomme les morceaux au même endroit, sans len() ni index répétés. L'intention se lit directement dans les motifs. Retenez la règle : structure et déstructuration penchent pour match ; condition unique penche pour if.

Vérifiez que l'essentiel de ce guide est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

6 questions
6 min.
70% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

  • match/case compare une valeur à des motifs et exécute le premier qui correspond, sans fallthrough.
  • Un motif littéral teste une égalité ; un nom nu est toujours une capture, jamais une comparaison.
  • Le wildcard _ joue le rôle de cas par défaut et correspond à tout.
  • Les motifs de séquence ([a, b, *reste]) déstructurent listes et tuples selon leur longueur.
  • Les motifs de mapping ({"cle": valeur}) testent la présence de clés, en ignorant les clés en trop.
  • Les motifs de classe (Point(x=0, y=y)) filtrent sur le type et les attributs d'un objet.
  • L'opérateur | regroupe des motifs ; une garde if ajoute une condition que le motif seul ne peut pas exprimer.
  • match gagne sur la structure d'une donnée ; if/elif reste préférable pour une condition unique.
  • Fonctionnalité disponible depuis Python 3.10 uniquement.

Le pattern matching prend toute sa valeur sur des données structurées reçues d'un fichier ou d'une API.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn