Cours
Aperçu de l'héritage
Au fur et à mesure que vous développerez vos projets et paquets Python, vous voudrez inévitablement utiliser des classes et appliquer le principe DRY (don't-repeat-yourself) tout en le faisant. L'héritage d'une classe est un moyen fantastique de créer une classe basée sur une autre classe afin de rester DRY. Ce billet couvrira des concepts plus avancés de l'héritage, et l'héritage de base ne sera pas couvert en profondeur. Nous allons vous présenter une introduction rapide, mais il existe de bien meilleures introductions détaillées. Voici quelques ressources pour commencer : Cours de programmation orientée objet en Python & Programmation orientée objet (POO) en Python : Tutoriel.
Qu'est-ce que l'héritage de classe ? Comme pour la génétique, une classe enfant peut "hériter" des attributs et des méthodes d'une classe parentale. Prenons un exemple avec un peu de code. Dans le bloc de code ci-dessous, nous démontrons l'héritage avec une classe Child
héritant d'une classe Parent
.
Entrée
class Parent:
def __init__(self):
self.parent_attribute = 'I am a parent'
def parent_method(self):
print('Back in my day...')
# Create a child class that inherits from Parent
class Child(Parent):
def __init__(self):
Parent.__init__(self)
self.child_attribute = 'I am a child'
# Create instance of child
child = Child()
# Show attributes and methods of child class
print(child.child_attribute)
print(child.parent_attribute)
child.parent_method()
Sortie
I am a child
I am a parent
Back in my day...
Nous constatons que la classe Child
a "hérité" des attributs et des méthodes de la classe Parent
. Sans aucune intervention de notre part, le site Parent.parent_method
fait partie de la classe Child
. Pour bénéficier des avantages de la méthode Parent.__init__()
, nous devons appeler explicitement la méthode et transmettre self
. En effet, lorsque nous avons ajouté une méthode __init__
à Child
, nous avons écrasé la méthode héritée __init__
.
Après ce bref aperçu, très incomplet, entrons dans le vif du sujet.
Introduction à super
Dans le cas le plus simple, la fonction super
peut être utilisée pour remplacer l'appel explicite à Parent.__init__(self)
. Notre exemple d'introduction de la première section peut être réécrit à l'aide de super
comme indiqué ci-dessous. Notez que le bloc de code ci-dessous est écrit en Python 3, les versions antérieures utilisent une syntaxe légèrement différente. En outre, la sortie a été omise car elle est identique au premier bloc de code.
class Parent:
def __init__(self):
self.parent_attribute = 'I am a parent'
def parent_method(self):
print('Back in my day...')
# Create a child class that inherits from Parent
class Child(Parent):
def __init__(self):
super().__init__()
self.child_attribute = 'I am a parent'
# Create instance of child
child = Child()
# Show attributes and methods of child class
print(child.child_attribute)
print(child.parent_attribute)
child.parent_method()
Pour être honnête, super
dans ce cas nous donne peu d'avantages, si ce n'est aucun. En fonction du nom de notre classe mère, nous pourrions économiser quelques frappes de clavier, et nous n'avons pas besoin de passer self
à l'appel à __init__
. Vous trouverez ci-dessous quelques avantages et inconvénients de l'utilisation de super
dans les cas d'héritage unique.
Cons
On peut faire valoir que l'utilisation de super
rend le code moins explicite. Rendre le code moins explicite va à l'encontre du zen de Python, qui stipule que "l'explicite vaut mieux que l'implicite".
Pour
Un argument en faveur de la maintenabilité peut être avancé pour super
, même dans le cas d'un héritage unique. Si, pour une raison quelconque, votre classe enfant change de modèle d'héritage (c'est-à-dire que la classe parentale change ou qu'il y a un passage à l'héritage multiple), il n'est pas nécessaire de trouver et de remplacer toutes les références persistantes à ParentClass.method_name()
; l'utilisation de super
permettra à tous les changements d'être répercutés dans la déclaration class
.
Maîtrisez vos compétences en matière de données avec DataCamp
Plus de 10 millions de personnes apprennent Python, R, SQL et d'autres compétences techniques grâce à nos cours pratiques élaborés par des experts du secteur.

super
et l'héritage multiple
Avant d'aborder la question de l'héritage multiple et de super
... Attention, cela peut devenir assez bizarre et compliqué.
Tout d'abord, qu'est-ce que l'héritage multiple ? Jusqu'à présent, l'exemple de code a porté sur une seule classe enfant héritant d'une seule classe parentale. Dans l'héritage multiple, il y a plus d'une classe mère. Une classe enfant peut hériter de 2, 3, 10, etc. classes parents.
C'est ici que les avantages de super
deviennent plus clairs. Outre l'économie de frappe que représente la référence aux différents noms des classes parentes, l'utilisation de super
avec plusieurs modèles d'héritage présente des avantages nuancés. En bref, si vous voulez utiliser l'héritage multiple, utilisez super
.
Héritage multiple sans super
Examinons un exemple d'héritage multiple qui évite de modifier les méthodes des parents et, par conséquent, super
.
Entrée
class B:
def b(self):
print('b')
class C:
def c(self):
print('c')
class D(B, C):
def d(self):
print('d')
d = D()
d.b()
d.c()
d.d()
Sortie
b
c
d
Ordre à résolution multiple
Ce résultat n'est pas très surprenant étant donné le concept d'héritage multiple. D
a hérité des méthodes x
et z
de ses classes mères, et tout va bien dans le monde... pour l'instant.
Que se passerait-il si B
et C
avaient tous deux une méthode portant le même nom ? C'est là qu'intervient un concept appelé "ordre de résolution multiple" ou MRO en abrégé. Le MRO d'une classe enfantine est ce qui décide où Python va chercher une méthode donnée, et quelle méthode sera appelée en cas de conflit.
Prenons un exemple.
Entrée
class B:
def x(self):
print('x: B')
class C:
def x(self):
print('x: C')
class D(B, C):
pass
d = D()
d.x()
print(D.mro())
Sortie
x: B
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class 'object'>]
Lorsque nous appelons la méthode héritée de x
, nous ne voyons que la sortie héritée de B
. Nous pouvons voir le MRO de notre classe D
en appelant la méthode de la classe mro
. La sortie de D.mro()
nous apprend ce qui suit : notre programme essaiera d'appeler les méthodes D
par défaut, puis de recourir à B
, puis à C
, et enfin à object
. Si elle n'est trouvée à aucun de ces endroits, nous obtiendrons une erreur indiquant que D
ne dispose pas de la méthode demandée.
Il convient de noter que, par défaut, chaque classe hérite de object
, et qu'elle se trouve à la fin de chaque MRO.
Héritage multiple, super
et le problème du diamant
Vous trouverez ci-dessous un exemple d'utilisation de super pour gérer le MRO d'init d'une manière bénéfique. Dans l'exemple, nous créons une série de classes de traitement de texte et combinons leurs fonctionnalités dans une autre classe à héritage multiple. Nous créerons 4 classes et la structure de l'héritage suivra la structure du diagramme ci-dessous.
Note : Cette structure est donnée à titre d'exemple et, sauf restrictions, il y aurait de meilleures façons de mettre en œuvre cette logique.
Il s'agit en fait d'un exemple du "problème du diamant" de l'héritage multiple. Son nom est bien sûr basé sur la forme de son design et sur le fait qu'il s'agit d'un problème assez déroutant.
Ci-dessous, le dessin est écrit à l'aide de super
.
Entrée
class Tokenizer:
"""Tokenize text"""
def __init__(self, text):
print('Start Tokenizer.__init__()')
self.tokens = text.split()
print('End Tokenizer.__init__()')
class WordCounter(Tokenizer):
"""Count words in text"""
def __init__(self, text):
print('Start WordCounter.__init__()')
super().__init__(text)
self.word_count = len(self.tokens)
print('End WordCounter.__init__()')
class Vocabulary(Tokenizer):
"""Find unique words in text"""
def __init__(self, text):
print('Start init Vocabulary.__init__()')
super().__init__(text)
self.vocab = set(self.tokens)
print('End init Vocabulary.__init__()')
class TextDescriber(WordCounter, Vocabulary):
"""Describe text with multiple metrics"""
def __init__(self, text):
print('Start init TextDescriber.__init__()')
super().__init__(text)
print('End init TextDescriber.__init__()')
td = TextDescriber('row row row your boat')
print('--------')
print(td.tokens)
print(td.vocab)
print(td.word_count)
Sortie
Start init TextDescriber.__init__()
Start WordCounter.__init__()
Start init Vocabulary.__init__()
Start Tokenizer.__init__()
End Tokenizer.__init__()
End init Vocabulary.__init__()
End WordCounter.__init__()
End init TextDescriber.__init__()
--------
['row', 'row', 'row', 'your', 'boat']
{'boat', 'your', 'row'}
5
Tout d'abord, nous constatons que la classe TextDescriber
a hérité de tous les attributs de l'arbre généalogique des classes. Grâce à l'héritage multiple, nous pouvons "combiner" les fonctionnalités de plusieurs classes.
Examinons maintenant les impressions issues des méthodes de la classe init
:
Chaque __init__
a été appelée une fois et une seule.
La classe TextDescriber
a hérité de 2 classes qui héritent de Tokenizer
. Pourquoi Tokenizer.__init__
n'a-t-il pas été appelé deux fois ?
Si nous remplacions tous nos appels à super
par l'ancienne méthode, nous aurions 2 appels à Tokenizer.__init__
. Les appels à super
"réfléchissent" un peu plus à notre modèle et évitent le voyage supplémentaire à A
.
Chaque __init__
a été lancée avant que toutes les autres ne soient terminées.
L'ordre des débuts et des fins de chaque __init__
est intéressant à noter au cas où vous tenteriez de définir un attribut dont le nom est en conflit avec celui d'une autre classe parente. L'attribut sera écrasé, ce qui peut prêter à confusion.
Dans notre cas, nous avons évité les conflits de noms avec les attributs hérités, de sorte que tout fonctionne comme prévu.
En résumé, le problème du diamant peut se compliquer rapidement et donner lieu à des résultats inattendus. Dans la plupart des cas de programmation, il est préférable d'éviter les conceptions compliquées.
Ce que nous avons appris
- Nous avons appris à connaître la fonction
super
et la manière dont elle peut être utilisée pour remplacerParentName.method
dans l'héritage unique. Cette pratique peut être plus facile à maintenir. - Nous avons appris ce qu'est l'héritage multiple et comment nous pouvons transmettre les fonctionnalités de plusieurs classes mères à une seule classe enfant.
- Nous avons appris ce qu'est l'ordre de résolution multiple et comment il décide de ce qui se passe dans l'héritage multiple lorsqu'il y a un conflit de nom entre les méthodes parentales.
- Nous avons découvert le problème du diamant et vu un exemple de la façon dont l'utilisation de
super
permet de naviguer dans le diamant.