L'héritage en Python : réutiliser une classe pour en écrire une autre

L'héritage évite de recopier une classe pour en écrire une variante : la nouvelle récupère les attributs et les méthodes du parent, et n'ajoute que ses différences.
5 min de lecture
Believemy logo

Définition

Deux classes finissent souvent par se ressembler beaucoup. Un utilisateur et un administrateur portent le même email et se présentent de la même façon ; seule une poignée de pouvoirs les sépare. Recopier la première pour écrire la seconde fonctionne le jour même, puis coince à la première correction : elle est à appliquer deux fois, et l'oubli d'un fichier ne se voit pas.

L'héritage règle exactement cet ennui. Il permet d'écrire une classe à partir d'une autre : la nouvelle récupère les attribut et les méthode de celle dont elle descend, et ne contient que ce qui la distingue. La première s'appelle la classe parente, ou classe de base ; celle qui hérite s'appelle la sous-classe.

Tout tient dans une paire de parenthèses posée après le nom de la nouvelle classe.

PYTHON
class Utilisateur:
    def __init__(self, email):
        self.email = email

    def se_presenter(self):
        return f"Compte {self.email}"


class Administrateur(Utilisateur):
    def supprimer(self, ressource):
        ...

Administrateur ne dit pas un mot de l'email, et pourtant chaque instance en possède un et sait se présenter. Le seul code à taper est celui qui n'existait pas chez le parent, et une correction faite sur Utilisateur profite aussitôt à tout ce qui en descend.


Les trois gestes d'une sous-classe

Une fois la parenté posée, la même question revient devant chaque méthode du parent : la garder, la remplacer, ou en ajouter une autre à côté ? Trois réponses, pas une de plus, et les avoir en tête lève la moitié des hésitations.

GesteCe qui se passe
HériterLa méthode du parent sert telle quelle, sans une ligne à réécrire
AjouterUne méthode nouvelle existe sur la sous-classe et pas sur le parent
RedéfinirUne méthode de même nom remplace celle du parent pour cette classe

Le troisième geste est celui qui surprend. Python ne fusionne rien et n'avertit de rien : à l'appel, il cherche le nom sur la classe de l'objet, remonte de parent en parent, et s'arrête sur la première correspondance. Une méthode se_presenter écrite dans Administrateur masque donc celle de Utilisateur sans la supprimer. Le schéma suit ce trajet sur trois classes.

Le chemin suivi par Python pour retrouver une méthodeTrois classes empilées, la classe de base en haut, l'instance tout en bas. Le chemin part de l'instance, passe par SuperAdmin qui ne possède pas la méthode, puis s'arrête sur Administrateur qui la possède. Le trajet vers Utilisateur reste en pointillés : sa version de la méthode existe toujours, mais elle n'est jamais atteinte.class Utilisateurjamais atteintedéfinit se_presenter, mais masquée iciclass Administrateur3 · trouvéehérite d'Utilisateur, définit se_presenterclass SuperAdmin2 · absentehérite d'Administrateur, sans se_presenteradmin.se_presenter()1 · départl'instance ne porte que ses donnéesLe premier qui la possède gagne, le reste est masqué.
Bon à savoir

Cette remontée ne s'arrête jamais dans le vide : toute classe descend de object, y compris celles écrites sans parenthèses. De là viennent les comportements qu'un objet possède avant la première ligne écrite pour lui.


super, et le piège de l'initialisation oubliée

Ajouter un attribut à la sous-classe suppose de redéfinir __init__, et c'est le cas le plus courant. Or redéfinir veut dire remplacer : la version du parent n'est plus appelée, donc les attributs qu'elle installait ne le sont plus. Il faut donc l'appeler avec super, avant d'ajouter ce qui appartient en propre à la sous-classe.

PYTHON
class Administrateur(Utilisateur):
    def __init__(self, email, niveau):
        super().__init__(email)
        self.niveau = niveau

La ligne super().__init__(email) fait exécuter au parent le travail qu'il sait déjà faire, sans en recopier une instruction. Elle vient en premier, parce que la suite en dépend souvent.

Attention

Oublier cet appel ne provoque aucune erreur : l'objet se crée normalement, en silence. Le manque n'apparaît qu'au premier accès à self.email, sous la forme d'une AttributeError levée très loin du fichier fautif. C'est l'erreur numéro un des débuts en objet.


Hériter ou composer

On choisit souvent l'héritage pour une mauvaise raison : deux classes partagent du code. Or partager du code n'est pas un lien de parenté. Le test qui tranche tient en une question : la sous-classe est-elle vraiment une sorte de parent ? Un administrateur est un utilisateur, l'héritage se justifie. Une commande n'est pas une liste de produits, elle en contient une : la réponse est un attribut, ce qu'on appelle la composition.

La raison de cette prudence est le couplage. Une sous-classe s'appuie sur les détails internes de son parent, y compris ceux qu'il ne promet à personne. Renommer une méthode interne, et du code écrit ailleurs cesse de fonctionner, parfois des mois plus tard.

Python ajoute une raison de ne pas hériter par réflexe : il n'exige aucun lien de parenté pour accepter un objet. Une fonction qui appelle se_presenter se contente de tout objet possédant cette méthode. Ce fonctionnement porte un nom, le duck typing.


Questions fréquentes

Question

Une classe peut-elle hériter de plusieurs parents ?

Oui, en listant les parents entre les parenthèses. Python les consulte de gauche à droite, selon un ordre appelé le MRO, que MaClasse.__mro__ affiche tel quel. En pratique, cet héritage multiple reste réservé à de petites classes très ciblées : dès qu'un même nom existe chez deux parents, deviner laquelle l'emporte demande un effort au lecteur suivant.

Question

Comment vérifier de quoi hérite un objet ?

Avec isinstance(objet, Utilisateur), qui répond vrai pour le parent comme pour toutes ses sous-classes. Une comparaison stricte de type répondrait faux pour un administrateur, alors qu'un administrateur est bel et bien un utilisateur. C'est donc la vérification à privilégier dès qu'une hiérarchie existe.

Question

Peut-on hériter d'une classe de la bibliothèque standard ?

Oui, et le cas le plus utile est la création d'exceptions maison, en héritant de Exception : le code appelant attrape alors cette erreur précise au lieu de tout attraper. Un dataclass peut également servir de base. La formation Python déroule cette progression sur un projet complet, du premier objet à la hiérarchie de classes.

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.