W3docs

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 matched

Règles à retenir :

  • match et case sont 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 case est 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 status

Motifs 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"))   # True

Les 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.

Avertissement

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 event

Point 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 5

Notez 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 type

Le 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 odd

Les 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 entered

En 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énarioMeilleur choix
Égalité simple contre quelques constantesL'un ou l'autre ; match est légèrement plus lisible
Correspondance sur une structure / forme de donnéesmatch — bien plus lisible
Déstructuration de valeurs lors de la correspondancematch — impossible avec if
Logique impliquant uniquement des conditions calculéesif/elif
Python 3.9 ou antérieurif/elif (match non disponible)
Exprimer clairement une table de décisionmatch

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 motifExemple de syntaxeCe qu'il correspond
Littéralcase 42:Valeur exacte
OUcase "yes" | "y":L'une des alternatives
Jokercase _:N'importe quoi (ignore la valeur)
Capturecase x:N'importe quoi, lie à x
Séquencecase [a, b, *rest]:Une séquence d'au moins 2 éléments
Mappagecase {"key": val}:Un dict contenant les clés données
Classecase Point(x=0, y=y):Une instance avec des attributs correspondants
AScase int() as n:Correspond et lie l'intégralité de la valeur
Gardecase x if x > 0:Motif + condition booléenne supplémentaire

Pratique

Pratique
Which Python version first introduced the match statement?
Which Python version first introduced the match statement?
Pratique
In a match block, what does a bare variable name in a case clause do?
In a match block, what does a bare variable name in a case clause do?
Pratique
Which pattern type would you use to match a dict that contains at least a 'type' key and extract its value?
Which pattern type would you use to match a dict that contains at least a 'type' key and extract its value?

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.

Was this page helpful?