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.
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.
| Geste | Ce qui se passe |
|---|---|
| Hériter | La méthode du parent sert telle quelle, sans une ligne à réécrire |
| Ajouter | Une méthode nouvelle existe sur la sous-classe et pas sur le parent |
| Redéfinir | Une 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.
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.
class Administrateur(Utilisateur):
def __init__(self, email, niveau):
super().__init__(email)
self.niveau = niveauLa 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.
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
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.
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.
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.