On oppose l'
héritage simple, dont nous venons de voir les aspects théoriques dans la section précédente, à l'
héritage multiple que nous verrons dans la prochaine section.
Il est temps d'aborder la syntaxe de l'héritage. Nous allons définir une première classe
A et une seconde classe
B qui hérite de
A.
Code : Python | class A:
"""Classe A, pour illustrer notre exemple d'héritage"""
pass # On laisse la définition vide, ce n'est qu'un exemple
class B(A):
"""Classe B, qui hérite de A.
Elle reprend les mêmes méthodes et attributs (dans cet exemple, la classe
A ne possède de toute façon ni méthode ni attribut)"""
pass
|
Vous pourrez expérimenter par la suite sur des exemples plus constructifs. Pour l'instant, l'important est de bien noter la syntaxe qui, comme vous le voyez, est des plus simples :
class MaClasse(MaClasseMere):. Dans la définition de la classe, entre le nom et les deux points, vous précisez entre parenthèses la classe dont elle doit hériter. Comme je l'ai dit, dans un premier temps, toutes les méthodes de la classe
A se retrouveront dans la classe
B.
J'ai essayé de mettre des constructeurs dans les deux classes mais, dans la classe fille, je ne retrouve pas les attributs déclarés dans ma classe mère, c'est normal ?
Tout à fait. Vous vous souvenez quand je vous ai dit que les méthodes étaient définies dans la classe, alors que les attributs étaient directement déclarés dans l'instance d'objet ? Vous le voyez bien de toute façon : c'est dans le constructeur qu'on déclare les attributs et on les écrit tous dans l'instance
self.
Quand une classe
B hérite d'une classe
A, les objets de type
B reprennent bel et bien les méthodes de la classe
A en même temps que celles de la classe
B. Mais, assez logiquement, ce sont celles de la classe
B qui sont appelées d'abord.
Si vous faites
objet_de_type_b.ma_methode(), Python va d'abord chercher la méthode
ma_methode dans la classe
B dont l'objet est directement issu. S'il ne trouve pas, il va chercher récursivement dans les classes dont hérite
B, c'est-à-dire
A dans notre exemple. Ce mécanisme est très important : il induit que si aucune méthode n'a été redéfinie dans la classe, on cherche dans la classe mère. On peut ainsi redéfinir une certaine méthode dans une classe et laisser d'autres directement hériter de la classe mère.
Petit code d'exemple :
Code : Python 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | class Personne:
"""Classe représentant une personne"""
def __init__(self, nom):
"""Constructeur de notre classe"""
self.nom = nom
self.prenom = "Martin"
def __str__(self):
"""Méthode appelée lors d'une conversion de l'objet en chaîne"""
return "{0} {1}".format(self.prenom, self.nom)
class AgentSpecial(Personne):
"""Classe définissant un agent spécial.
Elle hérite de la classe Personne"""
def __init__(self, nom, matricule):
"""Un agent se définit par son nom et son matricule"""
self.nom = nom
self.matricule = matricule
def __str__(self):
"""Méthode appelée lors d'une conversion de l'objet en chaîne"""
return "Agent {0}, matricule {1}".format(self.nom, self.matricule)
|
Vous voyez ici un exemple d'héritage simple. Seulement, si vous essayez de créer des agents spéciaux, vous risquez d'avoir de drôles de surprises :
Code : Python Console | >>> agent = AgentSpecial("Fisher", "18327-121")
>>> agent.nom
'Fisher'
>>> print(agent)
Agent Fisher, matricule 18327-121
>>> agent.prenom
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'AgentSpecial' object has no attribute 'prenom'
>>>
|
Argh… mais tu n'avais pas dit qu'une classe reprenait les méthodes et attributs de sa classe mère ?
Si. Mais en suivant bien l'exécution, vous allez comprendre : tout commence à la création de l'objet. Quel constructeur appeler ? S'il n'y avait pas de constructeur défini dans notre classe
AgentSpecial, Python appellerait celui de
Personne. Mais il en existe bel et bien un dans la classe
AgentSpecial et c'est donc celui-ci qui est appelé. Dans ce constructeur, on définit deux attributs,
nom et
matricule. Mais c'est tout : le constructeur de la classe
Personne n'est pas appelé, sauf si vous l'appelez explicitement dans le constructeur d'
AgentSpecial.
Dans le premier chapitre, je vous ai expliqué que
mon_objet.ma_methode() revenait au même que
MaClasse.ma_methode(mon_objet). Dans notre méthode
ma_methode, le premier paramètre
self sera
mon_objet. Nous allons nous servir de cette équivalence. La plupart du temps, écrire
mon_objet.ma_methode() suffit. Mais dans une relation d'héritage, il peut y avoir, comme nous l'avons vu, plusieurs méthodes du même nom définies dans différentes classes. Laquelle appeler ? Python choisit, s'il la trouve, celle définie directement dans la classe dont est issu l'objet, et sinon parcourt la hiérarchie de l'héritage jusqu'à tomber sur la méthode. Mais on peut aussi se servir de la notation
MaClasse.ma_methode(mon_objet) pour appeler une méthode précise d'une classe précise. Et cela est utile dans notre cas :
Code : Python 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 | class Personne:
"""Classe représentant une personne"""
def __init__(self, nom):
"""Constructeur de notre classe"""
self.nom = nom
self.prenom = "Martin"
def __str__(self):
"""Méthode appelée lors d'une conversion de l'objet en chaîne"""
return "{0} {1}".format(self.prenom, self.nom)
class AgentSpecial(Personne):
"""Classe définissant un agent spécial.
Elle hérite de la classe Personne"""
def __init__(self, nom, matricule):
"""Un agent se définit par son nom et son matricule"""
# On appelle explicitement le constructeur de Personne :
Personne.__init__(self, nom)
self.matricule = matricule
def __str__(self):
"""Méthode appelée lors d'une conversion de l'objet en chaîne"""
return "Agent {0}, matricule {1}".format(self.nom, self.matricule)
|
Si cela vous paraît encore un peu vague, expérimentez : c'est toujours le meilleur moyen. Entraînez-vous, contrôlez l'écriture des attributs, ou revenez au premier chapitre de cette partie pour vous rafraîchir la mémoire au sujet du paramètre
self, bien qu'à force de manipulations vous avez dû comprendre l'idée.
Reprenons notre code de tout à l'heure qui, cette fois, passe sans problème :
Code : Python Console | >>> agent = AgentSpecial("Fisher", "18327-121")
>>> agent.nom
'Fisher'
>>> print(agent)
Agent Fisher, matricule 18327-121
>>> agent.prenom
'Martin'
>>>
|
Cette fois, notre attribut
prenom se trouve bien dans notre agent spécial car le constructeur de la classe
AgentSpecial appelle explicitement celui de
Personne.
Vous pouvez noter également que, dans le constructeur d'
AgentSpecial, on n'instancie pas l'attribut
nom. Celui-ci est en effet écrit par le constructeur de la classe
Personne que nous appelons en lui passant en paramètre le nom de notre agent.
Notez que l'on pourrait très bien faire hériter une nouvelle classe de notre classe
Personne, la classe mère est souvent un modèle pour plusieurs classes filles.
Petite précision
Dans le chapitre précédent, je suis passé très rapidement sur l'héritage, ne voulant pas trop m'y attarder et brouiller les cartes inutilement. Mais j'ai expliqué brièvement que toutes les classes que vous créez héritent de la classe
object. C'est elle, notamment, qui définit toutes les méthodes spéciales que nous avons vues au chapitre précédent et qui connaît, bien mieux que nous, le mécanisme interne de l'objet. Vous devriez un peu mieux, à présent, comprendre le code du chapitre précédent. Le voici, en substance :
Code : Python | def __setattr__(self, nom_attribut, valeur_attribut):
"""Méthode appelée quand on fait objet.attribut = valeur"""
print("Attention, on modifie l'attribut {0} de l'objet !".format(nom_attribut))
object.__setattr__(self, nom_attribut, valeur_attribut)
|
En redéfinissant la méthode
__setattr__, on ne peut, dans le corps de cette méthode, modifier les valeurs de nos attributs comme on le fait habituellement (
self.attribut = valeur) car alors, la méthode s'appellerait elle-même. On fait donc appel à la méthode
__setattr__ de la classe
object, cette classe dont héritent implicitement toutes nos classes. On est sûr que la méthode de cette classe sait écrire une valeur dans un attribut, alors que nous ignorons le mécanisme et que nous n'avons pas besoin de le connaître : c'est la magie du procédé, une fois qu'on a bien compris le principe !
Deux fonctions très pratiques
Python définit deux fonctions qui peuvent se révéler utiles dans bien des cas :
issubclass et
isinstance.
issubclass
Comme son nom l'indique, elle vérifie si une classe est une sous-classe d'une autre classe. Elle renvoie
True si c'est le cas,
False sinon :
Code : Python Console | >>> issubclass(AgentSpecial, Personne) # AgentSpecial hérite de Personne
True
>>> issubclass(AgentSpecial, object)
True
>>> issubclass(Personne, object)
True
>>> issubclass(Personne, AgentSpecial) # Personne n'hérite pas d'AgentSpecial
False
>>>
|
isinstance
isinstance permet de savoir si un objet est issu d'une classe ou de ses classes filles :
Code : Python Console | >>> agent = AgentSpecial("Fisher", "18327-121")
>>> isinstance(agent, AgentSpecial) # Agent est une instance d'AgentSpecial
True
>>> isinstance(agent, Personne) # Agent est une instance héritée de Personne
True
>>>
|
Ces quelques exemples suffisent, je pense. Peut-être devrez-vous attendre un peu avant de trouver une utilité à ces deux fonctions mais ce moment viendra.