Implementer un Switch/Case en Python : Pattern Matching et Destructuration

Decouvrez comment implementer un pattern switch-case en Python avec une classe personnalisee, et maitrisez la destructuration d'arguments (*args, **kwargs) pour un code plus elegant et maintenable.

Mahmoud DEVO
Mahmoud DEVO
December 27, 2025 8 min read
Implementer un Switch/Case en Python : Pattern Matching et Destructuration

Introduction

Les developpeurs Python sont souvent confrontes a un defi recurrent : comment gerer elegamment de multiples cas dans une logique conditionnelle complexe ? Contrairement a des langages comme Java, C++ ou JavaScript qui disposent d’une structure switch-case native, Python a longtemps repose uniquement sur les structures if-elif-else.

Pourquoi ce sujet est-il important ? Dans le developpement moderne, la lisibilite et la maintenabilite du code sont cruciales. Les longues chaines de if-elif-else peuvent rapidement devenir difficiles a lire et a maintenir, surtout lorsqu’on gere plus de 5-6 cas differents.

Bonne nouvelle : depuis Python 3.10, le langage dispose du pattern matching avec match-case. Cependant, pour les versions anterieures ou pour des cas d’usage specifiques, une implementation personnalisee reste pertinente. Dans cet article, nous explorerons :

  • Une implementation elegante du pattern switch-case avec les context managers
  • La puissance de la destructuration d’arguments (*args et **kwargs)
  • Les bonnes pratiques et pieges a eviter

Cette approche, inspiree par la communaute Python, offre une alternative interessante qui exploite les capacites avancees du langage.

Creation d’une Classe Switch avec Context Manager

La solution consiste a creer une classe Switch qui exploite le protocole de context manager de Python. Ce pattern utilise les methodes speciales __enter__ et __exit__ pour gerer proprement l’entree et la sortie d’un bloc with.

Comprendre le Context Manager

Le context manager est un pattern fondamental en Python qui garantit :

  • Une initialisation propre des ressources
  • Un nettoyage automatique, meme en cas d’erreur
  • Une syntaxe claire avec le mot-cle with

Implementation de base

class Switch:
    """Classe implementant un pattern switch-case avec context manager."""

    def __init__(self, value):
        self._val = value
        self._matched = False

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        return False  # Ne pas supprimer les exceptions

    def __call__(self, *values):
        """Permet d'utiliser case(1, 2, 3) pour plusieurs valeurs."""
        if self._matched:
            return False
        if self._val in values:
            self._matched = True
            return True
        return False

    @property
    def default(self):
        """Cas par defaut si aucun match."""
        return not self._matched

Cette implementation offre plusieurs avantages :

  • Support de multiples valeurs par cas (case(1, 2, 3))
  • Cas par defaut avec la propriete default
  • Prevention de l’execution de plusieurs cas (fall-through)

Utilisation de la Classe Switch

Maintenant que nous avons notre classe, voyons comment l’utiliser dans differents scenarios.

Exemple simple

def convertir_nombre(value):
    with Switch(value) as case:
        if case(1):
            return 'un'
        if case(2):
            return 'deux'
        if case(3):
            return 'trois'
        if case.default:
            return 'inconnu'

# Tests
print(convertir_nombre(1))  # Output: un
print(convertir_nombre(2))  # Output: deux
print(convertir_nombre(99)) # Output: inconnu

Exemple avec plusieurs valeurs par cas

def classifier_jour(jour):
    with Switch(jour.lower()) as case:
        if case('samedi', 'dimanche'):
            return 'weekend'
        if case('lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi'):
            return 'jour ouvrable'
        if case.default:
            raise ValueError(f"Jour invalide: {jour}")

print(classifier_jour('samedi'))  # Output: weekend
print(classifier_jour('mardi'))   # Output: jour ouvrable

Exemple avec logique complexe

def calculer_remise(type_client, montant):
    with Switch(type_client) as case:
        if case('premium'):
            remise = 0.20
        elif case('standard'):
            remise = 0.10
        elif case('nouveau'):
            remise = 0.05
        elif case.default:
            remise = 0

    return montant * (1 - remise)

print(calculer_remise('premium', 100))   # Output: 80.0
print(calculer_remise('standard', 100))  # Output: 90.0

Comparaison avec Python 3.10+ Match-Case

Depuis Python 3.10, le langage dispose du pattern matching natif :

# Python 3.10+ - Pattern matching natif
def classifier_http_code(code):
    match code:
        case 200:
            return "OK"
        case 404:
            return "Not Found"
        case 500:
            return "Internal Server Error"
        case _:
            return "Unknown"

# Equivalent avec notre classe Switch (compatible Python 3.6+)
def classifier_http_code_legacy(code):
    with Switch(code) as case:
        if case(200):
            return "OK"
        if case(404):
            return "Not Found"
        if case(500):
            return "Internal Server Error"
        if case.default:
            return "Unknown"

Notre implementation reste utile pour :

  • Les projets utilisant Python < 3.10
  • Les cas ou l’on veut une logique de matching personnalisee

Destructuration d’Arguments (*args)

La destructuration des arguments est une fonctionnalite puissante de Python qui permet de manipuler des collections de valeurs de maniere flexible.

Unpacking avec l’operateur *

L’operateur * permet de “deballer” une sequence en arguments positionnels :

def calculer_moyenne(a, b, c):
    return (a + b + c) / 3

# Appel classique
print(calculer_moyenne(10, 20, 30))  # Output: 20.0

# Avec unpacking d'une liste
notes = [10, 20, 30]
print(calculer_moyenne(*notes))  # Output: 20.0

# Avec unpacking d'un tuple
notes_tuple = (15, 25, 35)
print(calculer_moyenne(*notes_tuple))  # Output: 25.0

Collecte d’arguments variables

La syntaxe *args permet de collecter un nombre variable d’arguments :

def somme(*nombres):
    """Calcule la somme de tous les arguments."""
    return sum(nombres)

print(somme(1, 2, 3))        # Output: 6
print(somme(1, 2, 3, 4, 5))  # Output: 15
print(somme())               # Output: 0

Combinaison d’arguments fixes et variables

def log_message(niveau, *messages):
    """Affiche des messages avec un niveau de log."""
    prefix = f"[{niveau.upper()}]"
    for msg in messages:
        print(f"{prefix} {msg}")

log_message("info", "Demarrage", "Connexion etablie", "Pret")
# Output:
# [INFO] Demarrage
# [INFO] Connexion etablie
# [INFO] Pret

Destructuration de Dictionnaires (**kwargs)

L’operateur ** permet de travailler avec des arguments nommes de maniere flexible.

Unpacking de dictionnaires

def creer_utilisateur(nom, email, role='user'):
    return {
        'nom': nom,
        'email': email,
        'role': role
    }

# Appel classique
user1 = creer_utilisateur('Alice', 'alice@example.com', 'admin')

# Avec unpacking d'un dictionnaire
donnees = {'nom': 'Bob', 'email': 'bob@example.com'}
user2 = creer_utilisateur(**donnees)

# Combinaison unpacking + argument explicite
user3 = creer_utilisateur(**donnees, role='moderator')

Collecte d’arguments nommes

def configurer_connexion(**options):
    """Configure une connexion avec des options flexibles."""
    config = {
        'host': 'localhost',
        'port': 5432,
        'timeout': 30,
        **options  # Fusion avec les options fournies
    }
    return config

# Utilisation
config1 = configurer_connexion(host='db.example.com', ssl=True)
print(config1)
# Output: {'host': 'db.example.com', 'port': 5432, 'timeout': 30, 'ssl': True}

Pattern complet : *args et **kwargs

def wrapper_fonction(func, *args, **kwargs):
    """Decorateur simple pour logger les appels de fonction."""
    print(f"Appel de {func.__name__}")
    print(f"  Args: {args}")
    print(f"  Kwargs: {kwargs}")
    return func(*args, **kwargs)

def ma_fonction(a, b, option=False):
    return a + b if not option else a * b

# Utilisation
result = wrapper_fonction(ma_fonction, 5, 3, option=True)
print(f"Resultat: {result}")  # Output: Resultat: 15

Bonnes Pratiques

Voici les recommandations essentielles pour utiliser efficacement ces patterns :

1. Privilegier match-case pour Python 3.10+

Si votre projet utilise Python 3.10 ou superieur, preferez la syntaxe native match-case qui est optimisee et plus lisible :

# Prefere pour Python 3.10+
match status_code:
    case 200 | 201:
        return "Success"
    case 400 | 404:
        return "Client Error"
    case _:
        return "Unknown"

2. Documenter les cas attendus

Toujours documenter les valeurs attendues, surtout pour les cas par defaut :

def traiter_commande(status: str) -> str:
    """
    Traite le status d'une commande.

    Args:
        status: Un des status valides ('pending', 'processing', 'shipped', 'delivered')
    """
    with Switch(status) as case:
        # ... implementation

3. Utiliser les type hints avec *args et **kwargs

from typing import Any

def ma_fonction(*args: int, **kwargs: str) -> dict[str, Any]:
    """Les type hints ameliorent la documentation et l'IDE support."""
    return {'args': args, 'kwargs': kwargs}

4. Eviter les effets de bord dans les cas

Chaque cas doit etre independant et ne pas modifier d’etat global :

# Mauvais
resultat = None
with Switch(value) as case:
    if case(1):
        resultat = process_one()  # Effet de bord

# Bon
def get_resultat(value):
    with Switch(value) as case:
        if case(1):
            return process_one()  # Retour immediat

5. Limiter le nombre de cas

Si vous avez plus de 7-8 cas, envisagez un dictionnaire de mapping :

# Mieux pour beaucoup de cas
HANDLERS = {
    'create': handle_create,
    'update': handle_update,
    'delete': handle_delete,
    # ... autres handlers
}

def dispatch(action: str, data: dict):
    handler = HANDLERS.get(action, handle_default)
    return handler(data)

Pieges Courants a Eviter

1. Oublier le cas par defaut

Sans cas par defaut, les valeurs inattendues passent silencieusement :

# Dangereux - pas de gestion des cas inconnus
with Switch(status) as case:
    if case('active'):
        return True
    if case('inactive'):
        return False
    # Que se passe-t-il pour status='pending' ?

# Correct
with Switch(status) as case:
    if case('active'):
        return True
    if case('inactive'):
        return False
    if case.default:
        raise ValueError(f"Status inconnu: {status}")

2. Confondre *args et **kwargs dans l’ordre

L’ordre des parametres est strict : def f(pos, *args, kw_only, **kwargs) :

# ERREUR de syntaxe
def mauvaise_fonction(**kwargs, *args):  # SyntaxError!
    pass

# Correct
def bonne_fonction(*args, **kwargs):
    pass

# Avec arguments nommes obligatoires
def complete_fonction(obligatoire, *args, option=None, **kwargs):
    pass

3. Modifier un dictionnaire pendant l’iteration avec **kwargs

# Dangereux
def modifier_config(**config):
    for key in config:
        if key.startswith('temp_'):
            del config[key]  # RuntimeError!

# Correct
def modifier_config(**config):
    keys_to_remove = [k for k in config if k.startswith('temp_')]
    for key in keys_to_remove:
        del config[key]

4. Unpacking avec nombre incorrect d’elements

# Erreur a l'execution
def besoin_trois(a, b, c):
    return a + b + c

liste_courte = [1, 2]
besoin_trois(*liste_courte)  # TypeError: missing 1 required argument

# Solution : valider avant unpacking
if len(liste_courte) == 3:
    besoin_trois(*liste_courte)

Conclusion

Dans cet article, nous avons explore deux concepts puissants de Python :

  1. Le pattern Switch-Case : Une implementation elegante utilisant les context managers, compatible avec les versions de Python anterieures a 3.10. Cette approche demontre la flexibilite de Python et sa capacite a implementer des patterns de programmation avances.

  2. La destructuration d’arguments : Les operateurs * et ** offrent une flexibilite remarquable pour manipuler les arguments de fonctions, que ce soit pour l’unpacking de sequences/dictionnaires ou pour la collecte d’arguments variables.

Ces techniques sont essentielles pour ecrire du code Python idiomatique, maintenable et elegant. Elles sont particulierement utiles dans :

  • La creation de decorateurs et wrappers
  • L’implementation d’APIs flexibles
  • La gestion de configurations dynamiques
  • Le pattern strategy et le dispatch de commandes

Pour aller plus loin, nous vous recommandons d’explorer le pattern matching structural de Python 3.10+, qui offre des capacites encore plus avancees comme le matching sur les types et les attributs d’objets.

N’hesitez pas a partager vos questions ou retours d’experience dans les commentaires !

Advertisement

In-Article Ad

Dev Mode

Share this article

Mahmoud DEVO

Mahmoud DEVO

Senior Full-Stack Developer

I'm a passionate full-stack developer with 10+ years of experience building scalable web applications. I write about Vue.js, Node.js, PostgreSQL, and modern DevOps practices.

Enjoyed this article?

Subscribe to get more tech content delivered to your inbox.

Related Articles