Le duck typing en Python : ce que fait un objet compte plus que ce qu'il est

Le duck typing juge un objet sur les méthodes qu'il porte plutôt que sur sa classe : Python vérifie au moment de l'appel, jamais avant.
5 min de lecture
Believemy logo

Définition

Vous écrivez une fonction qui doit faire parler un objet. Quels objets a-t-elle le droit de recevoir ? Beaucoup de langages exigent une réponse écrite à l'avance : une classe précise, ou une interface à respecter. Python répond autrement, et sa réponse porte un nom : le duck typing.

La règle veut que Python juge un objet sur ce qu'il sait faire, et non sur la classe dont il descend. La formule d'origine le dit mieux qu'une définition : si un objet marche comme un canard et cancane comme un canard, autant le traiter comme un canard. Aucune déclaration préalable, aucune interface à signer : il suffit que la méthode appelée existe sur l'objet au moment où on l'appelle.

Deux classes sans le moindre lien de parenté, et une fonction qui les traite exactement pareil :

PYTHON
class Canard:
    def cancane(self):
        return "coin"

class Robot:
    def cancane(self):
        return "coin synthétique"

def faire_parler(animal):
    # Aucune vérification : on appelle, et on verra bien.
    print(animal.cancane())

faire_parler(Canard())
faire_parler(Robot())

La fonction faire_parler ne demande jamais le type de ce qu'elle reçoit : elle appelle, et Python va chercher le nom cancane sur l'objet au dernier moment. Un objet écrit ailleurs, par quelqu'un qui n'avait jamais entendu parler d'elle, fonctionnera donc sans une ligne d'adaptation : la compatibilité ne se déclare pas, elle se constate.


Ce que Python vérifie, et à quel moment

Si rien n'est contrôlé à l'avance, qu'est-ce qui est contrôlé, et quand ? Beaucoup de langages vérifient la compatibilité avant l'exécution, sur une hiérarchie déclarée à l'avance ; Python la vérifie pendant l'exécution, sur les noms réellement présents. Le tableau les met face à face, Java ou C# à gauche, Python à droite.

Typage nominalDuck typing
La question posée est « de quel type est cet objet »La question posée est « cet objet a-t-il ce qu'il faut »
Le contrôle a lieu avant que le programme tourneLe contrôle a lieu à la ligne qui appelle
Il faut hériter ou implémenter une interfaceIl suffit de porter le bon nom de méthode
Une incompatibilité bloque la compilationUne incompatibilité lève une TypeError

Ce déplacement du contrôle a une conséquence très concrète : une classe n'a pas besoin de connaître celles qui l'utiliseront, ni l'inverse. Deux bibliothèques développées séparément se branchent l'une sur l'autre du moment qu'elles s'accordent sur des noms de méthodes, sans aucune dépendance commune à installer.


Les protocoles, ces contrats que personne ne signe

Le duck typing n'est pas une tolérance accordée à votre code : Python se l'applique d'abord à lui-même. Ses propres constructions ne réclament pas un type précis, elles réclament une méthode magique, c'est-à-dire une méthode dont le nom est encadré de doubles soulignés. Voici les quatre que l'on rencontre le plus tôt.

Ce que vous voulez faireLa méthode à écrire
Passer l'objet à len__len__
Le parcourir avec for__iter__, qui en fait un itérable
L'ouvrir dans un bloc with__enter__ et __exit__
L'afficher lisiblement__str__

Rien à hériter, rien à déclarer : écrire la méthode suffit. C'est pourquoi une fonction qui accepte « un fichier » accepte en réalité tout objet muni des méthodes read et write, et pourquoi un faux fichier tenu en mémoire remplace le vrai dans les tests, sans qu'une ligne du code testé ne bouge.

Attention

Deux classes sans rapport peuvent porter la même méthode avec un sens différent. Un objet muni d'une méthode send n'envoie pas forcément un message : Python l'acceptera sans broncher et le problème apparaîtra bien plus loin, sous une forme qui ne ressemblera plus à une erreur de type.


Le prix à payer : l'erreur arrive tard

Cette liberté a une contrepartie. Rien ne prévient qu'un objet est inadapté avant la ligne qui l'utilise : dépourvu de la méthode attendue, il traverse sans bruit les appels intermédiaires et déclenche une AttributeError au fond de la pile, loin de l'endroit où il a été fabriqué. Confiez un chat à la fonction du début, et le message d'erreur ne parlera ni du chat ni de la fonction.

PYTHON
class Chat:
    def miauler(self):
        return "miaou"

faire_parler(Chat())

# Rien ne bronche jusqu'à l'appel de animal.cancane()
# AttributeError: 'Chat' object has no attribute 'cancane'

Deux réflexes limitent la casse. Le premier consiste à documenter l'attente au lieu de la laisser deviner : une annotation de type ou une docstring qui nomme les méthodes requises fait apparaître le problème dans l'éditeur, avant l'exécution.

Le second consiste à entourer l'appel d'un try quand recevoir un objet approximatif fait partie des cas prévus. Interroger l'objet avant de s'en servir paraît plus prudent, mais cette prudence finit par refuser des objets qui auraient parfaitement fonctionné.


Questions fréquentes

Question

Faut-il vérifier le type avec isinstance avant d'appeler ?

Rarement. Un isinstance referme la porte que le duck typing vient d'ouvrir : il refuse un objet parfaitement compatible au seul motif qu'il n'appartient pas à la bonne famille. La culture Python préfère tenter l'appel et rattraper l'échec. Le contrôle garde sa place à l'entrée des données venues de l'extérieur.

Question

Le duck typing rend-il les annotations de type inutiles ?

Non, les deux se complètent. Le module typing propose Protocol, qui décrit les méthodes attendues sans imposer d'héritage : le vérificateur y gagne une garantie et l'appelant garde sa liberté de fournir n'importe quel objet conforme. Un outil comme mypy lit ces protocoles et signale l'objet incompatible avant même que le programme tourne.

Question

Quelle est la différence avec l'héritage ?

L'héritage partage du code et crée une famille : une sous-classe reçoit les méthodes de sa classe mère. Le duck typing ne partage qu'un vocabulaire : deux objets qui n'ont rien en commun deviennent interchangeables parce qu'ils répondent au même nom de méthode. La formation Python montre où passe la limite entre les deux.

Termes connexes

Découvrez notre glossaire Python

Parcourez les termes et définitions les plus couramment utilisés dans le domaine du développement avec Python.

Partager cet article

Tu veux nous aider ? Fais un lien vers cet article sur tes réseaux ou encore mieux : sur ton site, dans un article ou dans ta newsletter.