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: helloLe 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 :
| Feature | Python match-case | C/Java switch-case |
|---|---|---|
| Fall-through | No (each case is independent) | Yes (requires break) |
| Pattern types | Literals, sequences, mappings, classes, OR, guards | Mostly literals only |
| Destructuring | Built-in (bind variables from matched structure) | Not supported |
| Variable binding | Captures values from matched pattern | Not supported |
| Default case | case _: | default: |
| Expression/statement | Statement (no return value) | Statement |
| Minimum Python | 3.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 Found2. 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: 42Piè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 47. 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 oddLes 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 exceededCela 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 modeAlternatives 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)) # infDispatch 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
| Approach | Python Version | Best For | Complexity | Performance |
|---|---|---|---|---|
match-case | 3.10+ | Complex patterns, destructuring, type matching | High expressiveness | Comparable to if/elif |
if/elif/else | All | Simple value comparisons, < 6 cases | Simple | O(n) linear scan |
| Dict dispatch | All | Many cases, value → function mapping | Medium | O(1) lookup |
| Dict + lambda | All | Many cases, value → expression mapping | Medium | O(1) lookup |
| Enum dispatch | All | Typed constants, exhaustive matching | Medium | O(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) # 14Machine 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| Approach | Best 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":
passVé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 logsFoire 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
- Python Try Except: Error Handling Guide
- Python Type Hints: Complete Tutorial
- Python Decorators Explained
- Python Collections Module Guide
- Python Enumerate Function
- Python Assert Statement