Skip to content
Thèmes
Python
Python Switch Case: match-case Statement Explained (With Examples)

Python Switch Case : explication de l’instruction match-case

Mis à jour le

Vous écrivez un processeur de commandes en Python. La fonction prend une chaîne comme "quit", "save", "load" ou "help" et doit exécuter une logique différente pour каждой commande. En JavaScript ou en C, vous utiliseriez une instruction switch. En Python, vous écrivez une chaîne de blocs if/elif/else — cinq lignes pour deux commandes, quinze pour six, trente pour douze. La fonction devient un mur de comparaisons fastidieux à lire, facile à mal ordonner et pénible à étendre.

Si votre requête de recherche est python switch case, commencez ici. Pour la référence plus approfondie sur le structural pattern matching, voir Python Match Case. Pour une présentation plus simple de la dispatch par dictionnaire, voir Python Switch Case.

C’était une vraie limite de Python pendant plus de 30 ans. Toutes les autres grandes langues avaient une construction switch ou case, mais Python s’appuyait entièrement sur les chaînes if/elif et la dispatch par dictionnaire. Guido van Rossum a rejeté à plusieurs reprises les propositions de switch-case, estimant que if/elif suffisait.

Cela a changé dans Python 3.10 avec PEP 634 (opens in a new tab), qui a introduit l’instruction match-case — la réponse de Python au switch-case, mais bien plus puissante. Au lieu de simplement comparer des valeurs, elle peut déstructurer des objets, faire correspondre des motifs, lier des variables et appliquer des conditions. Il s’agit de structural pattern matching, pas seulement d’un remplacement du switch.

Syntaxe rapide : match-case en 30 secondes

def handle_command(command):
    match command:
        case "quit":
            print("Exiting program")
        case "save":
            print("Saving file")
        case "help":
            print("Showing help")
        case _:
            print(f"Unknown command: {command}")
 
handle_command("save")   # Saving file
handle_command("hello")  # Unknown command: hello

Le mot-clé match évalue l’expression sujet une seule fois. Chaque case définit un motif. Python teste les motifs de haut en bas et exécute la première correspondance. Le joker _ est le cas par défaut — il correspond à n’importe quoi.

match-case vs switch-case : différences clés

Si vous venez de C, Java, JavaScript ou Go, voici les différences essentielles :

FeaturePython match-caseC/Java switch-case
Fall-throughNo (each case is independent)Yes (requires break)
Pattern typesLiterals, sequences, mappings, classes, OR, guardsMostly literals only
DestructuringBuilt-in (bind variables from matched structure)Not supported
Variable bindingCaptures values from matched patternNot supported
Default casecase _:default:
Expression/statementStatement (no return value)Statement
Minimum Python3.10+N/A

La différence la plus importante : pas de fall-through. Chaque bloc de cas est isolé. Vous n’avez jamais besoin d’une instruction break, et vous n’exécutez jamais accidentellement le cas suivant.

Types de motifs : ce que match-case peut faire

L’instruction match-case de Python prend en charge sept types de motifs. C’est ce qui la rend fondamentalement plus puissante qu’une instruction switch traditionnelle.

1. Motifs littéraux

Fait correspondre des valeurs exactes — chaînes, nombres, booléens, None :

def classify_status(code):
    match code:
        case 200:
            return "OK"
        case 301:
            return "Moved Permanently"
        case 404:
            return "Not Found"
        case 500:
            return "Internal Server Error"
        case _:
            return f"Unknown status: {code}"
 
print(classify_status(404))  # Not Found

2. Motifs OR (plusieurs valeurs)

Utilisez l’opérateur | pour faire correspondre plusieurs valeurs dans un même cas :

def classify_http_status(code):
    match code:
        case 200 | 201 | 204:
            return "Success"
        case 301 | 302 | 307 | 308:
            return "Redirect"
        case 400 | 401 | 403 | 404:
            return "Client Error"
        case 500 | 502 | 503:
            return "Server Error"
        case _:
            return "Unknown"

Cela remplace le motif courant if code in (200, 201, 204):.

3. Motifs de capture (liaison de variables)

Un nom nu dans un case capture la valeur correspondante dans une variable :

def process_value(data):
    match data:
        case 0:
            print("Zero")
        case value:
            print(f"Got non-zero value: {value}")
 
process_value(42)  # Got non-zero value: 42

Piège important : un nom nu capture toujours — il ne compare pas à une variable existante. C’est l’erreur la plus fréquente :

# WRONG — this does NOT compare against expected_code
expected_code = 200
match response_code:
    case expected_code:  # This captures response_code into expected_code!
        print("Match")
 
# RIGHT — use a guard or literal
match response_code:
    case code if code == expected_code:
        print("Match")

Pour faire correspondre des constantes, utilisez des noms pointés (comme http.HTTPStatus.OK) ou des gardes.

4. Motifs de séquence (listes et tuples)

Déstructurez directement les listes et les tuples dans le motif :

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((3, 0)))   # On x-axis at 3
print(process_point((2, 5)))   # Point at (2, 5)

Vous pouvez utiliser *rest pour capturer les éléments restants :

def first_and_rest(items):
    match items:
        case []:
            return "Empty list"
        case [single]:
            return f"Single item: {single}"
        case [first, *rest]:
            return f"First: {first}, remaining: {rest}"
 
print(first_and_rest([1, 2, 3, 4]))  # First: 1, remaining: [2, 3, 4]

5. Motifs de mapping (dictionnaires)

Fait correspondre des dictionnaires et déstructure les valeurs :

def handle_event(event):
    match event:
        case {"type": "click", "x": x, "y": y}:
            print(f"Click at ({x}, {y})")
        case {"type": "keypress", "key": key}:
            print(f"Key pressed: {key}")
        case {"type": "scroll", "direction": direction}:
            print(f"Scroll {direction}")
        case _:
            print("Unknown event")
 
handle_event({"type": "click", "x": 100, "y": 200})
# Click at (100, 200)
 
handle_event({"type": "keypress", "key": "Enter", "modifiers": ["Ctrl"]})
# Key pressed: Enter  (extra keys are ignored)

Les motifs de mapping correspondent si les clés spécifiées sont présentes — les clés supplémentaires du dictionnaire sont ignorées silencieusement. Cela les rend idéaux pour traiter des données JSON ou des réponses d’API.

6. Motifs de classe

Fait correspondre des instances de classe et extrait des attributs :

from dataclasses import dataclass
 
@dataclass
class Point:
    x: float
    y: float
 
@dataclass
class Circle:
    center: Point
    radius: float
 
@dataclass
class Rectangle:
    corner: Point
    width: float
    height: float
 
def describe_shape(shape):
    match shape:
        case Circle(center=Point(x=0, y=0), radius=r):
            return f"Circle at origin with radius {r}"
        case Circle(center=center, radius=r):
            return f"Circle at ({center.x}, {center.y}) with radius {r}"
        case Rectangle(width=w, height=h) if w == h:
            return f"Square with side {w}"
        case Rectangle(width=w, height=h):
            return f"Rectangle {w}x{h}"
 
print(describe_shape(Circle(Point(0, 0), 5)))
# Circle at origin with radius 5
 
print(describe_shape(Rectangle(Point(1, 1), 4, 4)))
# Square with side 4

7. Clauses de garde

Ajoutez des conditions if à n’importe quel motif pour un filtrage supplémentaire :

def classify_number(n):
    match n:
        case n if n < 0:
            return "Negative"
        case 0:
            return "Zero"
        case n if n % 2 == 0:
            return "Positive even"
        case _:
            return "Positive odd"
 
print(classify_number(-5))  # Negative
print(classify_number(4))   # Positive even
print(classify_number(7))   # Positive odd

Les gardes permettent d’ajouter des conditions arbitraires au-delà de ce que les motifs peuvent exprimer. Le motif est d’abord testé, puis la condition de garde est évaluée.

Exemple réel : analyseur de réponse d’API JSON

L’une des utilisations les plus pratiques de match-case consiste à analyser des réponses d’API ou des données JSON :

import json
 
def process_api_response(response: dict) -> str:
    match response:
        case {"status": "success", "data": {"users": [*users]}}:
            return f"Found {len(users)} users"
 
        case {"status": "success", "data": {"user": {"name": name, "email": email}}}:
            return f"User: {name} ({email})"
 
        case {"status": "error", "code": 401}:
            return "Authentication required"
 
        case {"status": "error", "code": code, "message": msg}:
            return f"Error {code}: {msg}"
 
        case {"status": "error"}:
            return "Unknown error"
 
        case _:
            return "Unrecognized response format"
 
# Test with different responses
print(process_api_response({
    "status": "success",
    "data": {"users": ["Alice", "Bob", "Charlie"]}
}))
# Found 3 users
 
print(process_api_response({
    "status": "error",
    "code": 429,
    "message": "Rate limit exceeded"
}))
# Error 429: Rate limit exceeded

Cela remplace de longues chaînes if/elif imbriquées avec des vérifications isinstance() et des appels à .get() sur les dictionnaires. La version match-case exprime directement la structure attendue.

Exemple réel : analyseur d’arguments en ligne de commande

import sys
 
def parse_args(args: list[str]):
    match args:
        case [program]:
            print(f"Usage: {program} <command> [options]")
 
        case [_, "init", project_name]:
            print(f"Initializing project: {project_name}")
 
        case [_, "init"]:
            print("Error: project name required")
 
        case [_, "build", "--release"]:
            print("Building in release mode")
 
        case [_, "build"]:
            print("Building in debug mode")
 
        case [_, "test", *test_files] if test_files:
            print(f"Running tests: {', '.join(test_files)}")
 
        case [_, "test"]:
            print("Running all tests")
 
        case [_, unknown, *_]:
            print(f"Unknown command: {unknown}")
 
parse_args(["app", "init", "my-project"])  # Initializing project: my-project
parse_args(["app", "test", "a.py", "b.py"])  # Running tests: a.py, b.py
parse_args(["app", "build", "--release"])  # Building in release mode

Alternatives avant Python 3.10 : switch-case sans match-case

Si vous utilisez Python 3.9 ou une version antérieure, ou si match-case est excessif pour une simple correspondance de valeurs, voici les alternatives établies.

Chaînes if/elif/else

L’approche la plus directe :

def handle_command(command):
    if command == "quit":
        print("Exiting program")
    elif command == "save":
        print("Saving file")
    elif command == "load":
        print("Loading file")
    elif command == "help":
        print("Showing help")
    else:
        print(f"Unknown command: {command}")

Quand l’utiliser : lorsque vous avez moins de 5 à 6 cas et une logique simple.

Dispatch par dictionnaire

Associez des valeurs à des fonctions via un dictionnaire :

def cmd_quit():
    print("Exiting program")
 
def cmd_save():
    print("Saving file")
 
def cmd_help():
    print("Showing help")
 
commands = {
    "quit": cmd_quit,
    "save": cmd_save,
    "help": cmd_help,
}
 
def handle_command(command):
    action = commands.get(command)
    if action:
        action()
    else:
        print(f"Unknown command: {command}")

Quand l’utiliser : lorsque vous mappez des valeurs vers des actions simples, surtout avec de nombreux cas. Recherche en O(1) au lieu de O(n) pour if/elif.

Dispatch par dictionnaire avec arguments

def calculate(operation, a, b):
    ops = {
        "+": lambda a, b: a + b,
        "-": lambda a, b: a - b,
        "*": lambda a, b: a * b,
        "/": lambda a, b: a / b if b != 0 else float("inf"),
    }
    func = ops.get(operation)
    if func is None:
        raise ValueError(f"Unknown operation: {operation}")
    return func(a, b)
 
print(calculate("+", 10, 3))  # 13
print(calculate("/", 10, 0))  # inf

Dispatch basé sur Enum

Pour un code typé et auto-documenté :

from enum import Enum
 
class Direction(Enum):
    NORTH = "north"
    SOUTH = "south"
    EAST = "east"
    WEST = "west"
 
def move(direction: Direction, x: int, y: int) -> tuple[int, int]:
    offsets = {
        Direction.NORTH: (0, 1),
        Direction.SOUTH: (0, -1),
        Direction.EAST: (1, 0),
        Direction.WEST: (-1, 0),
    }
    dx, dy = offsets[direction]
    return (x + dx, y + dy)
 
print(move(Direction.NORTH, 0, 0))  # (0, 1)

Comparaison : quelle approche utiliser

ApproachPython VersionBest ForComplexityPerformance
match-case3.10+Complex patterns, destructuring, type matchingHigh expressivenessComparable to if/elif
if/elif/elseAllSimple value comparisons, < 6 casesSimpleO(n) linear scan
Dict dispatchAllMany cases, value → function mappingMediumO(1) lookup
Dict + lambdaAllMany cases, value → expression mappingMediumO(1) lookup
Enum dispatchAllTyped constants, exhaustive matchingMediumO(1) lookup

Rule of thumb:

  • Use match-case when you need destructuring, type matching, or complex patterns
  • Use if/elif for simple comparisons with few branches
  • Use dict dispatch for many simple value-to-action mappings
  • Use enum dispatch when the set of values is fixed and typed

Modèles et recettes courants

Dispatch basé sur le type

def serialize(value):
    match value:
        case bool():  # Must come before int — bool is a subclass of int
            return "true" if value else "false"
        case int() | float():
            return str(value)
        case str():
            return f'"{value}"'
        case list():
            items = ", ".join(serialize(v) for v in value)
            return f"[{items}]"
        case dict():
            pairs = ", ".join(
                f'{serialize(k)}: {serialize(v)}' for k, v in value.items()
            )
            return "{" + pairs + "}"
        case None:
            return "null"
        case _:
            raise TypeError(f"Cannot serialize {type(value)}")
 
print(serialize({"name": "Alice", "scores": [95, 87, 92], "active": True}))
# {"name": "Alice", "scores": [95, 87, 92], "active": true}

Important : placez bool() avant int() parce que True et False sont des instances de int en Python.

Correspondance de motifs imbriqués

def evaluate(expr):
    """Simple math expression evaluator."""
    match expr:
        case int(n) | float(n):
            return n
        case ("+", left, right):
            return evaluate(left) + evaluate(right)
        case ("-", left, right):
            return evaluate(left) - evaluate(right)
        case ("*", left, right):
            return evaluate(left) * evaluate(right)
        case ("/", left, right):
            divisor = evaluate(right)
            if divisor == 0:
                raise ZeroDivisionError("Division by zero")
            return evaluate(left) / divisor
        case ("neg", operand):
            return -evaluate(operand)
        case _:
            raise ValueError(f"Invalid expression: {expr}")
 
# (3 + 4) * 2
result = evaluate(("*", ("+", 3, 4), 2))
print(result)  # 14

Machine d’état avec match-case

def tokenize(text):
    """Simple tokenizer using match-case as a state machine."""
    tokens = []
    i = 0
    while i < len(text):
        match text[i]:
            case ' ' | '\t' | '\n':
                i += 1  # Skip whitespace
            case '+' | '-' | '*' | '/':
                tokens.append(("OP", text[i]))
                i += 1
            case '(' | ')':
                tokens.append(("PAREN", text[i]))
                i += 1
            case c if c.isdigit():
                j = i
                while j < len(text) and text[j].isdigit():
                    j += 1
                tokens.append(("NUM", int(text[i:j])))
                i = j
            case c if c.isalpha():
                j = i
                while j < len(text) and text[j].isalnum():
                    j += 1
                tokens.append(("ID", text[i:j]))
                i = j
            case c:
                raise SyntaxError(f"Unexpected character: {c}")
    return tokens
 
print(tokenize("x + 42 * (y - 3)"))
# [('ID', 'x'), ('OP', '+'), ('NUM', 42), ('OP', '*'), ('PAREN', '('),
#  ('ID', 'y'), ('OP', '-'), ('NUM', 3), ('PAREN', ')')]

Performance : match-case vs if/elif vs dict

Pour une correspondance simple de valeurs, les trois approches sont suffisamment rapides pour que la différence n’ait pas d’importance en pratique. Voici des résultats de benchmark pour une comparaison avec 10 valeurs de chaîne :

import timeit
 
commands = ["cmd_0", "cmd_1", "cmd_2", "cmd_3", "cmd_4",
            "cmd_5", "cmd_6", "cmd_7", "cmd_8", "cmd_9"]
 
# if/elif approach
def if_elif_dispatch(cmd):
    if cmd == "cmd_0": return 0
    elif cmd == "cmd_1": return 1
    elif cmd == "cmd_2": return 2
    elif cmd == "cmd_3": return 3
    elif cmd == "cmd_4": return 4
    elif cmd == "cmd_5": return 5
    elif cmd == "cmd_6": return 6
    elif cmd == "cmd_7": return 7
    elif cmd == "cmd_8": return 8
    elif cmd == "cmd_9": return 9
    else: return -1
 
# dict dispatch approach
dispatch_dict = {f"cmd_{i}": i for i in range(10)}
def dict_dispatch(cmd):
    return dispatch_dict.get(cmd, -1)
 
# match-case approach
def match_dispatch(cmd):
    match cmd:
        case "cmd_0": return 0
        case "cmd_1": return 1
        case "cmd_2": return 2
        case "cmd_3": return 3
        case "cmd_4": return 4
        case "cmd_5": return 5
        case "cmd_6": return 6
        case "cmd_7": return 7
        case "cmd_8": return 8
        case "cmd_9": return 9
        case _: return -1
ApproachBest case (first match)Worst case (last match)Miss (no match)
if/elif~80ns~400ns~420ns
dict dispatch~60ns~60ns~65ns
match-case~90ns~450ns~470ns

Point clé : la dispatch par dictionnaire a une complexité O(1) constante quelle que soit la position. Les chaînes if/elif et match-case sont toutes deux des parcours linéaires en O(n). Pour moins d’environ 20 cas, la différence est négligeable. Choisissez en fonction de la lisibilité, pas des performances.

Erreurs courantes et comment les éviter

Erreur 1 : utiliser match-case avant Python 3.10

# This causes SyntaxError in Python 3.9 and earlier
match command:  # SyntaxError: invalid syntax
    case "quit":
        pass

Vérifiez votre version de Python : python --version. Si vous êtes en 3.9 ou avant, utilisez if/elif ou la dispatch par dictionnaire.

Erreur 2 : comparer avec des variables

# WRONG — captures, does NOT compare
STATUS_OK = 200
match response_code:
    case STATUS_OK:  # This always matches and overwrites STATUS_OK!
        print("OK")
 
# RIGHT — use dotted name
class Status:
    OK = 200
    NOT_FOUND = 404
 
match response_code:
    case Status.OK:
        print("OK")
    case Status.NOT_FOUND:
        print("Not Found")
 
# RIGHT — use a guard
match response_code:
    case code if code == STATUS_OK:
        print("OK")

Erreur 3 : oublier le joker par défaut

# DANGEROUS — unmatched values silently do nothing
match command:
    case "save":
        save_file()
    case "load":
        load_file()
    # No default — "delete" silently falls through with no action
 
# SAFE — always include a default case
match command:
    case "save":
        save_file()
    case "load":
        load_file()
    case _:
        raise ValueError(f"Unknown command: {command}")

Erreur 4 : mauvais ordre avec des motifs qui se recouvrent

# WRONG — the first case catches everything
match value:
    case x:            # Captures ANY value — always matches!
        print(f"Got: {x}")
    case 42:           # Never reached
        print("The answer")
 
# RIGHT — specific patterns first, general last
match value:
    case 42:
        print("The answer")
    case x:
        print(f"Got: {x}")

Utiliser match-case en data science

Si vous travaillez sur des pipelines de traitement de données, match-case est utile pour gérer différents formats de données et opérations de nettoyage :

import csv
from pathlib import Path
 
def load_data(source):
    """Load data from different source types."""
    match source:
        case str() as path if path.endswith(".csv"):
            with open(path) as f:
                return list(csv.DictReader(f))
        case str() as path if path.endswith(".json"):
            import json
            with open(path) as f:
                return json.load(f)
        case list() as records:
            return records
        case dict() as single_record:
            return [single_record]
        case _:
            raise TypeError(f"Unsupported data source: {type(source)}")

Pour l’exploration interactive des données, des outils comme PyGWalker (opens in a new tab) permettent de visualiser directement des DataFrames sans écrire de code de visualisation — utile lorsque vous voulez inspecter rapidement les données produites par votre pipeline de traitement.

Si vous construisez des scripts de traitement de données qui utilisent match-case pour des branchements complexes, RunCell (opens in a new tab) fournit un environnement Jupyter alimenté par l’IA où vous pouvez tester le pattern matching de manière interactive avec de vrais échantillons de données.

match-case avec dataclasses et named tuples

L’instruction match-case de Python fonctionne particulièrement bien avec les dataclasses et les named tuples :

from dataclasses import dataclass
from typing import Optional
 
@dataclass
class LogEntry:
    level: str
    message: str
    error: Optional[Exception] = None
 
def handle_log(entry: LogEntry):
    match entry:
        case LogEntry(level="CRITICAL", error=err) if err is not None:
            send_alert(f"CRITICAL with error: {err}")
            restart_service()
        case LogEntry(level="CRITICAL", message=msg):
            send_alert(f"CRITICAL: {msg}")
        case LogEntry(level="ERROR", error=err) if err is not None:
            log_to_file(f"ERROR: {err}")
        case LogEntry(level="WARNING" | "ERROR", message=msg):
            log_to_file(msg)
        case LogEntry(level="INFO" | "DEBUG"):
            pass  # Ignore low-priority logs

Foire aux questions

Python a-t-il une instruction switch ?

Python 3.10+ a l’instruction match-case, qui est la version Python du switch-case. Elle va au-delà du switch traditionnel avec le structural pattern matching — vous pouvez déstructurer des objets, faire correspondre des motifs et lier des variables. Pour Python 3.9 et les versions antérieures, utilisez des chaînes if/elif/else ou la dispatch par dictionnaire comme alternatives.

Quelle est la différence entre match-case et switch-case ?

Le match-case de Python n’a pas de fall-through (aucun break requis), prend en charge la correspondance de motifs (séquences, mappings, classes), peut déstructurer et lier des variables à partir des motifs correspondants, et prend en charge les clauses de garde avec des conditions if. Le switch-case traditionnel en C/Java ne fait que comparer des valeurs littérales.

match-case est-il plus rapide que if/elif en Python ?

Pour une correspondance simple de valeurs, leurs performances sont similaires — les deux effectuent un parcours linéaire. La dispatch par dictionnaire est plus rapide (O(1)) pour de nombreux cas. La différence de performance est négligeable pour moins d’une vingtaine de cas. Choisissez en fonction de la lisibilité et de la complexité des motifs, pas des performances.

Puis-je utiliser match-case avec Python 3.9 ?

Non. L’instruction match-case nécessite Python 3.10 ou une version ultérieure. Pour les versions antérieures, utilisez des chaînes if/elif/else ou la dispatch par dictionnaire.

Pourquoi la comparaison de variable dans match-case ne fonctionne-t-elle pas ?

Un nom nu comme case x: capture n’importe quelle valeur dans x — il ne compare pas à une variable existante. Pour comparer à une variable, utilisez une clause de garde (case val if val == x:) ou des noms pointés (case MyClass.CONSTANT:).

Que signifie le soulignement _ dans match-case ?

Le motif joker _ correspond à n’importe quelle valeur sans la lier à une variable. C’est le cas par défaut — comme default: dans les instructions switch. Placez-le toujours en dernier.

Guides connexes

📚