Accéder au contenu principal

Introduction à l'héritage multiple et à super()

Guide d'introduction à l'héritage multiple, à la fonction super() et au problème du diamant pour un Pythoniste.
Actualisé 15 janv. 2025  · 8 min de lecture

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.

Commencer à apprendre
learner-on-couch@2x.jpg

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, superet 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.

problème du diamant

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 remplacer ParentName.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.
Sujets

En savoir plus sur Python

cours

Introduction to Python

4 hr
6M
Master the basics of data analysis with Python in just four hours. This online course will introduce the Python interface and explore popular packages.
Afficher les détailsRight Arrow
Commencer le cours
Voir plusRight Arrow
Apparenté

blog

Les 32 meilleures questions d'entretien sur AWS et leurs réponses pour 2024

Un guide complet pour explorer les questions d'entretien AWS de base, intermédiaires et avancées, ainsi que des questions basées sur des situations réelles. Il couvre tous les domaines, garantissant ainsi une stratégie de préparation bien équilibrée.
Zoumana Keita 's photo

Zoumana Keita

30 min

blog

Célébration de Saghar Hazinyar : Une boursière de DataCamp Donates et une diplômée de Code to Inspire

Découvrez le parcours inspirant de Saghar Hazinyar, diplômée de Code to Inspire, qui a surmonté les défis en Afghanistan et s'est épanouie grâce à une bourse de DataCamp Donates.
Fereshteh Forough's photo

Fereshteh Forough

4 min

blog

Les 20 meilleures questions d'entretien pour les flocons de neige, à tous les niveaux

Vous êtes actuellement à la recherche d'un emploi qui utilise Snowflake ? Préparez-vous à répondre à ces 20 questions d'entretien sur le flocon de neige pour décrocher le poste !
Nisha Arya Ahmed's photo

Nisha Arya Ahmed

20 min

blog

Q2 2023 DataCamp Donates Digest

DataCamp Donates a offert plus de 20k bourses d'études à nos partenaires à but non lucratif au deuxième trimestre 2023. Découvrez comment des apprenants défavorisés et assidus ont transformé ces opportunités en réussites professionnelles qui ont changé leur vie.
Nathaniel Taylor-Leach's photo

Nathaniel Taylor-Leach

blog

Nous avons fait don de bourses DataCamp Premium à un million de personnes, et ce n'est pas fini.

Réparties entre nos deux programmes d'impact social, DataCamp Classrooms et #DCDonates, les bourses offrent un accès illimité à tout ce que DataCamp Premium a à offrir.
Nathaniel Taylor-Leach's photo

Nathaniel Taylor-Leach

blog

2022-2023 Rapport annuel DataCamp Classrooms

À l'aube de la nouvelle année scolaire, DataCamp Classrooms est plus motivé que jamais pour démocratiser l'apprentissage des données, avec plus de 7 650 nouveaux Classrooms ajoutés au cours des 12 derniers mois.
Nathaniel Taylor-Leach's photo

Nathaniel Taylor-Leach

8 min

Voir plusVoir plus