Python 3.10 (octobre 2021) a introduit l’instruction match/case — une forme évoluée de « switch » bien plus puissante que dans d’autres langages. Elle permet non seulement de comparer des valeurs, mais aussi de destructurer des collections, de filtrer par type, et de capturer des parties des données. Le TOSA Avancé la teste depuis 2023.
39.1 Pourquoi match / case ?
Imaginez traiter un message sous différentes formes :
Plus concis, plus lisible, et surtout : on capture directement les parties intéressantes dans des variables.
39.2 Syntaxe de base
match expression:case motif1:# code si expression correspond à motif1case motif2:# code si expression correspond à motif2case _:# cas par défaut (wildcard)
Comme pour if/elif, le premier case qui correspond est exécuté — un seul.
Le wildcard _
_ (un simple underscore) est un motif spécial qui correspond à tout. On l’utilise comme cas par défaut.
Si aucun motif ne correspond et qu’aucun _ n’est présent, match ne fait rien (contrairement à un if/elif où ce serait pareil). Pas d’erreur, mais le bloc est simplement ignoré.
Bonne pratique : toujours inclure un case _: pour traiter explicitement le cas imprévu.
39.3 Motifs littéraux
Les plus simples : comparer à une valeur littérale.
200 → Succès
301 → Redirection
404 → Erreur client
502 → Erreur serveur
418 → Autre
39.4 Capture de variables
Un case peut capturer une valeur dans une variable — très pratique.
def decrire(valeur):match valeur:case0:return"zéro"case1:return"un"case n:returnf"autre nombre : {n}"# n capture la valeurprint(decrire(0))print(decrire(1))print(decrire(42))
zéro
un
autre nombre : 42
⚠️ Piège : un nom nu capture TOUJOURS
Un nom en minuscules tout seul dans un casecapture la valeur — il ne la compare pas !
MAX =100match x:case MAX: # ❌ capture dans MAX, ne compare PAS à 100 !print("Maximum atteint")
Pour comparer à une constante, utilisez un nom qualifié (avec un point) :
class Limites: MAX =100match x:case Limites.MAX: # ✅ comparaisonprint("Maximum")
Ou des littéraux entiers : case 100: marche sans souci.
39.5 Motifs sur les séquences
match peut destructurer une liste ou un tuple.
Nombre fixe d’éléments
def traiter_point(p):match p: case (0, 0):return"Origine" case (x, 0):returnf"Sur l'axe X à {x}" case (0, y):returnf"Sur l'axe Y à {y}" case (x, y):returnf"Point ({x}, {y})"case _:return"Pas un point 2D"print(traiter_point((0, 0)))print(traiter_point((3, 0)))print(traiter_point((0, 5)))print(traiter_point((3, 4)))print(traiter_point((1, 2, 3)))
Origine
Sur l'axe X à 3
Sur l'axe Y à 5
Point (3, 4)
Pas un point 2D
Nombre variable avec *
Le * capture « le reste » dans une liste.
def decouper_liste(liste):match liste: case []:return"Vide" case [x]:returnf"Un seul : {x}" case [x, y]:returnf"Deux : {x}, {y}" case [premier, *milieu, dernier]:returnf"Premier={premier}, dernier={dernier}, milieu={milieu}"case _:return"Autre"for l in [[], [1], [1, 2], [1, 2, 3, 4, 5]]:print(decouper_liste(l))
Vide
Un seul : 1
Deux : 1, 2
Premier=1, dernier=5, milieu=[2, 3, 4]
Notez que le motif [premier, *milieu, dernier]capture directement la structure — équivalent à vouloir faire liste[0], liste[1:-1], liste[-1] mais en une ligne.
39.6 Motifs sur les dictionnaires
On peut matcher sur certaines clés d’un dict, sans exiger toutes les clés.
Données reçues : [1, 2, 3]
Erreur : Forbidden
OK (sans données)
Format inconnu
Le match de dict est partiel
Le motif {"statut": "ok"} correspond à tout dict contenant au moins cette clé. Le dict peut avoir d’autres clés — elles sont ignorées. C’est différent d’une comparaison d’égalité.
39.7 Motifs par type (class patterns)
C’est la fonctionnalité la plus puissante du pattern matching. On peut matcher sur le type d’un objet et capturer ses attributs.
Type simple
def identifier(x):match x:caseint():returnf"Entier : {x}"casefloat():returnf"Flottant : {x}"casestr():returnf"Chaîne : '{x}'"caselist():returnf"Liste de {len(x)} éléments"case _:return"Type inconnu"for v in [42, 3.14, "hello", [1, 2, 3], (1, 2)]:print(identifier(v))
Entier : 42
Flottant : 3.14
Chaîne : 'hello'
Liste de 3 éléments
Type inconnu
Les parenthèses () après le nom de classe sont obligatoires — c’est une syntaxe spécifique au pattern matching.
Type + capture
On peut à la fois matcher sur le type et capturer la valeur :
def doubler(x):match x:caseint(n):return n *2casestr(s):return s + scaselist(l):return l + lcase _:returnNoneprint(doubler(5))print(doubler("abc"))print(doubler([1, 2]))
10
abcabc
[1, 2, 1, 2]
Avec des classes personnelles : attributs
Pour les classes POO, on peut matcher sur leurs attributs.
class Point:__match_args__= ("x", "y") # ordre des attributs pour le match positionneldef__init__(self, x, y):self.x = xself.y = ydef position(p):match p:case Point(0, 0):return"Origine"case Point(x=0, y=y):returnf"Sur axe Y en y={y}"case Point(x=x, y=0):returnf"Sur axe X en x={x}"case Point(x, y): # capture positionnelle grâce à __match_args__returnf"Point ({x}, {y})"print(position(Point(0, 0)))print(position(Point(0, 5)))print(position(Point(3, 0)))print(position(Point(3, 4)))
Origine
Sur axe Y en y=5
Sur axe X en x=3
Point (3, 4)
__match_args__
L’attribut spécial __match_args__ définit l’ordre des paramètres pour le match positionnel. Sans lui, seul le match par nom (Point(x=..., y=...)) fonctionne.
Avec @dataclass, __match_args__ est généré automatiquement d’après l’ordre des champs :
from dataclasses import dataclass@dataclassclass Point: x: int y: intp = Point(3, 4)match p:case Point(x, y): # ça marche directementprint(f"({x}, {y})")
(3, 4)
39.8 Clauses if (gardes)
On peut ajouter une condition supplémentaire à un case avec if.
def categoriser(age):match age:caseint(n) if n <0:return"Âge invalide"caseint(n) if n <18:return"Mineur"caseint(n) if n <65:return"Adulte"caseint(n):return"Senior"case _:return"Pas un âge valide"for age in [-5, 10, 30, 75, "trente"]:print(f"{age} → {categoriser(age)}")
-5 → Âge invalide
10 → Mineur
30 → Adulte
75 → Senior
trente → Pas un âge valide
Les gardes sont très puissantes combinées avec la capture — on filtre finement sans imbriquer des if.
39.9 Récapitulatif des motifs
Type de motif
Exemple
Effet
Littéral
case 42:
Comparaison d’égalité
Wildcard
case _:
N’importe quoi
Capture
case n:
Capture dans n
Alternative
case 1 \| 2 \| 3:
L’un des trois
Séquence
case [a, b]:
Liste/tuple de 2 éléments
Séquence + *
case [x, *rest]:
Déballage
Dict
case {"k": v}:
Dict contenant au moins cette clé
Type
case str():
Instance de la classe
Type + attributs
case Point(x=0):
Type + valeurs d’attributs
Garde
case n if n > 0:
Condition supplémentaire
39.10match vs if/elif : quand choisir ?
Guide pratique
match/case brille quand vous :
Comparez une valeur à plusieurs formes (nombre, chaîne, liste, dict).
Voulez destructurer et capturer en même temps.
Matchez sur types d’objets (polymorphisme).
Avez plus de 2-3 branches qui partagent un même sujet.
if/elif reste préférable quand :
Les conditions sont indépendantes (pas un même sujet à matcher).
Vous ne faites qu’une comparaison simple entre deux valeurs.
Vous avez besoin de Python < 3.10 (support legacy).
39.11 Attention : rétrocompatibilité
match/case est disponible à partir de Python 3.10. Il ne fonctionne pas en Python 3.9 ou antérieur.
# Python 3.9 : SyntaxErrormatch x:case1: ...
Si votre code doit fonctionner sur des versions plus anciennes, utilisez if/elif/else.
🧩 Quiz 10.1 — Pattern matching
Question 1
À partir de quelle version de Python match/case existe-t-il ?
3.7
3.8
3.9
3.10
🔍 Réponse
d) 3.10 — octobre 2021. C’est la version de référence du TOSA.
Question 2
Que fait case _: ?
Ne fait rien
Capture la valeur dans une variable _
Match n’importe quelle valeur (cas par défaut)
Termine le match
🔍 Réponse
c) — _ est un wildcard, il matche toujours. Équivalent du default: dans d’autres langages.
Question 3
Que s’affiche-t-il ?
x =5match x:case1|2|3:print("petit")case4|5|6:print("moyen")case _:print("grand")
petit
moyen
grand
petit moyen
🔍 Réponse
b) moyen — le | permet de combiner plusieurs valeurs dans un seul case.
x =5match x:case1|2|3:print("petit")case4|5|6:print("moyen")case _:print("grand")
MAX =100x =50match x:case MAX:print("Max atteint")case _:print(f"Pas encore : {MAX}")
Max atteint
Pas encore : 100
Pas encore : 50
Une erreur
🔍 Réponse
d) Une erreur — piège TOSA : MAX dans un case est un nom nu, donc Python le traite comme une capture (qui matche tout), pas comme une comparaison à 100. Le compilateur détecte statiquement que case _: devient alors inaccessible et refuse de compiler dès Python 3.10 :
SyntaxError: name capture 'MAX' makes remaining patterns unreachable
Pour comparer à la constante, utilisez un nom qualifié (Limites.MAX) ou un littéral (case 100:).
Sans le case _:, le code compile — et on voit bien la capture à l’œuvre : MAX est écrasé par la valeur de x.
MAX =100x =50match x:case MAX:print("Max atteint") # s'exécute car MAX capture 50print(f"MAX vaut maintenant : {MAX}") # MAX = 50 !
Max atteint
MAX vaut maintenant : 50
Question 7
Comment ajouter une condition supplémentaire à un case ?
case n if n > 0:
case n when n > 0:
case n where n > 0:
case n && n > 0:
🔍 Réponse
a) case n if n > 0: — les gardes utilisent le mot-clé if.
x =5match x:case n if n >0:print(f"Positif : {n}")case n if n <0:print(f"Négatif : {n}")case0:print("Zéro")
Positif : 5
✏️ Exercice 10.1 — Calculatrice par match
Écrivez une fonction calculer(expression) qui reçoit un tuple (op, a, b) et effectue l’opération correspondante.
def calculer(expression):match expression: case ("+", a, b): ... case ("-", a, b): ... case ("*", a, b): ... case ("/", a, b) if b !=0: ...case _: ...print(calculer(("+", 3, 5)))print(calculer(("*", 4, 7)))print(calculer(("/", 10, 2)))print(calculer(("/", 10, 0)))print(calculer(("?", 1, 2)))
def calculer(expression):match expression: case ("+", a, b):return a + b case ("-", a, b):return a - b case ("*", a, b):return a * b case ("/", a, b) if b !=0:return a / b case ("/", _, 0):return"Division par zéro"case _:return"Opération inconnue"print(calculer(("+", 3, 5)))print(calculer(("-", 10, 4)))print(calculer(("*", 4, 7)))print(calculer(("/", 10, 2)))print(calculer(("/", 10, 0)))print(calculer(("?", 1, 2)))
8
6
28
5.0
Division par zéro
Opération inconnue
Le motif ("+", a, b) destructure le tuple et capture a et b en même temps.
La gardeif b != 0 filtre les divisions légitimes.
Le cas ("/", _, 0) gère spécifiquement la division par zéro. Le _ ignore le premier argument (divisé).
L’ordre des case compte : les cas plus spécifiques d’abord.
✏️ Exercice 10.2 — Analyse de messages
Écrivez une fonction traiter_message(msg) qui réagit à différents formats de messages dict.