logo oujood
🔍

Le duck typing en Python

En Python, ce qui compte n'est pas le type d'un objet mais ce qu'il sait faire. Le duck typing exploite ce principe pour écrire du code plus flexible sans héritage forcé.

OUJOOD.COM

Le duck typing tire son nom d'un principe simple : « Si ça marche comme un canard et que ça cancane comme un canard, alors c'est un canard. » En Python, cela signifie que vous n'avez pas besoin de vérifier le type d'un objet pour l'utiliser — il suffit qu'il possède les méthodes ou attributs dont vous avez besoin.

C'est une différence fondamentale avec des langages comme Java où une fonction qui attend un objet de type Animal refuse tout ce qui n'en hérite pas. En Python, si l'objet sait faire ce qu'on lui demande, il passe. Ce cours explique ce que ça change concrètement et comment en tirer parti.

Un exemple immédiat

La fonction len() fonctionne sur une chaîne, une liste, un tuple, un dictionnaire — sans que ces types n'aient de lien d'héritage entre eux. Python ne vérifie pas le type : il vérifie simplement que l'objet possède une méthode __len__.

  📋 Copier le code

# len() fonctionne sur tout objet qui a __len__
print(len("bonjour"))       # str → 7
print(len([1, 2, 3]))       # list → 3
print(len({"a": 1, "b": 2}))# dict → 2
print(len((10, 20)))        # tuple → 2

# Votre propre classe fonctionne aussi si elle a __len__
class Panier:
    def __init__(self, articles):
        self.articles = articles

    def __len__(self):
        return len(self.articles)

panier = Panier(["pain", "lait", "café"])
print(len(panier))
# Résultat : 3

Duck typing vs vérification de type

Voici la différence concrète entre une approche qui vérifie le type et une approche duck typing :

  📋 Copier le code

# ❌ Approche rigide : vérification de type
def faire_parler_rigide(animal):
    if isinstance(animal, Chien) or isinstance(animal, Chat):
        animal.parler()
    else:
        print("Je ne connais pas cet animal.")

# ✅ Approche duck typing : on essaie, on attrape si ça échoue
def faire_parler(animal):
    try:
        animal.parler()
    except AttributeError:
        print(f"{type(animal).__name__} ne sait pas parler.")

class Chien:
    def parler(self):
        print("Woof !")

class Chat:
    def parler(self):
        print("Miaou !")

class Robot:
    def parler(self):
        print("Bip boop.")

class Pierre:
    pass

# Fonctionne avec tout objet qui a une méthode parler()
for objet in [Chien(), Chat(), Robot(), Pierre()]:
    faire_parler(objet)
# Résultat :
# Woof !
# Miaou !
# Bip boop.
# Pierre ne sait pas parler.

Exemple pratique : sauvegarder des données

Une fonction qui écrit des données dans un fichier peut accepter n'importe quel objet qui se comporte comme un fichier — un vrai fichier ouvert, un StringIO en mémoire, ou votre propre classe — du moment qu'il a une méthode write().

  📋 Copier le code

import io

def sauvegarder(destination, contenu):
    # On ne vérifie pas le type — on demande juste write()
    destination.write(contenu)

# Avec un vrai fichier
with open("sortie.txt", "w", encoding="utf-8") as f:
    sauvegarder(f, "Bonjour depuis un fichier.")

# Avec un StringIO (fichier en mémoire — pratique pour les tests)
buffer = io.StringIO()
sauvegarder(buffer, "Bonjour depuis la mémoire.")
print(buffer.getvalue())
# Résultat : Bonjour depuis la mémoire.

# Avec votre propre classe qui a write()
class Journal:
    def __init__(self):
        self.lignes = []

    def write(self, texte):
        self.lignes.append(texte)
        print(f"[Journal] {texte}")

journal = Journal()
sauvegarder(journal, "Entrée du jour.")
# Résultat : [Journal] Entrée du jour.

Duck typing et les protocoles Python

Python formalise certains comportements duck typing sous forme de protocoles : un ensemble de méthodes spéciales à implémenter pour qu'un objet soit compatible avec une fonctionnalité. Implémenter __iter__ et __next__ rend un objet itérable. Implémenter __enter__ et __exit__ le rend utilisable avec with.

  📋 Copier le code

# Rendre une classe itérable (protocole itérateur)
class Compte_a_rebours:
    def __init__(self, debut):
        self.debut = debut
        self.actuel = debut

    def __iter__(self):
        return self

    def __next__(self):
        if self.actuel < 0:
            raise StopIteration
        valeur = self.actuel
        self.actuel -= 1
        return valeur

# Fonctionne dans une boucle for comme n'importe quel itérable
for n in Compte_a_rebours(5):
    print(n, end=" ")
# Résultat : 5 4 3 2 1 0

# Fonctionne aussi avec list(), sum(), max()...
print(list(Compte_a_rebours(3)))
# Résultat : [3, 2, 1, 0]

LBYL vs EAFP : deux styles de duck typing

Face à une opération qui peut échouer, Python reconnaît deux approches. LBYL (Look Before You Leap) vérifie d'abord les conditions. EAFP (Easier to Ask Forgiveness than Permission) tente l'opération et attrape l'exception si ça rate. EAFP est le style idiomatique Python.

  📋 Copier le code

donnees = {"nom": "Alice", "age": 30}

# Style LBYL : vérifier avant d'agir
if "email" in donnees:
    print(donnees["email"])
else:
    print("Pas d'email.")

# Style EAFP : tenter et gérer l'échec (préféré en Python)
try:
    print(donnees["email"])
except KeyError:
    print("Pas d'email.")

# Encore plus court avec .get()
print(donnees.get("email", "Pas d'email."))
# Résultat : Pas d'email.

Quand vérifier le type malgré tout ?

Le duck typing ne signifie pas qu'on n'utilise jamais isinstance(). Il y a des cas légitimes : valider les paramètres d'une fonction publique, adapter le comportement selon le type reçu, ou générer un message d'erreur clair. La règle est de ne pas rejeter un objet uniquement parce qu'il n'hérite pas du bon type.

  📋 Copier le code

def afficher_elements(collection):
    # isinstance() légitime : message d'erreur explicite
    if not hasattr(collection, "__iter__"):
        raise TypeError(
            f"Attendu un itérable, reçu {type(collection).__name__}"
        )
    for element in collection:
        print(element)

afficher_elements([1, 2, 3])
# Résultat : 1  2  3

afficher_elements("abc")
# Résultat : a  b  c

try:
    afficher_elements(42)
except TypeError as e:
    print(e)
# Résultat : Attendu un itérable, reçu int

Par carabde | Mis à jour le 21 avril 2026