Apprenez en lisant dans l'ordre

Héritage multiple et MRO — Hériter de plusieurs parents

Apprends l'héritage multiple en Python. Combine des parents avec class Enfant(A, B):, vois comment l'ordre de résolution des méthodes (MRO) choisit parmi les méthodes de même nom, et vérifie l'ordre avec __mro__.

La dernière fois on a couvert l'héritage simple, la surcharge et super(). Python te permet d'hériter de plusieurs parents en même temps — c'est l'héritage multiple. Cette fois on couvre la syntaxe et la règle qui décide quel parent est appelé quand plusieurs ont le même nom de méthode : l'ordre de résolution des méthodes (MRO).

La syntaxe de base

La syntaxe est simple — liste les classes parentes séparées par des virgules. Écrire class Duck(Animal, Swimmer, Flyer): veut dire que Duck hérite des attributs et méthodes de tous les Animal, Swimmer et Flyer.

Dans l'exemple ci-dessous, Animal porte le nom et le comportement de parole, Swimmer ajoute la natation, Flyer ajoute le vol — et Duck rassemble les trois en une créature capable.

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        pass


class Swimmer:
    def swim(self):
        return f"{self.name} nage"


class Flyer:
    def fly(self):
        return f"{self.name} vole"


class Duck(Animal, Swimmer, Flyer):    # héritage multiple
    def speak(self):
        return f"{self.name} fait coin"


duck = Duck("Donald")
print(duck.speak())   # Donald fait coin
print(duck.swim())    # Donald nage
print(duck.fly())     # Donald vole
Duck hérite de trois parents
Animalname / speakSwimmerswimFlyerflyDuckspeak (override)héritehéritehérite
Duck hérite des attributs et __init__ de Animal, de swim de Swimmer et de fly de Flyer. Seul speak est surchargé par Duck lui-même.

Construis le Duck qui rassemble trois parents.

① Définis class Animal: avec __init__(self, name) qui assigne self.name = name.

② Dans class Swimmer:, définis swim(self) qui retourne f"{self.name} nage". Fais pareil pour Flyer avec fly(self).

③ Définis class Duck(Animal, Swimmer, Flyer): avec speak(self) qui retourne f"{self.name} fait coin".

④ Construis duck = Duck("Donald") et print les valeurs de retour de speak(), swim() et fly().

(Une fois que ça tourne correctement, l'explication apparaîtra.)

Éditeur Python

Exécuter le code pour voir le résultat

Ordre de résolution des méthodes (MRO) — Quel parent gagne ?

Les choses se compliquent quand plusieurs parents ont une méthode de même nom. Si Swimmer et Flyer définissaient tous les deux une méthode move, laquelle s'exécute quand tu appelles move sur une instance Duck ?

Python utilise l'ordre de résolution des méthodes (MRO) — un ordre de recherche fixe — pour parcourir les classes du haut vers le bas et exécuter le premier match. Pour l'héritage multiple, l'ordre est de gauche à droite comme écrit dans class Duck(A, B, C):. Donc la méthode de même nom de A gagne ; si elle n'est pas là, on essaie B, puis C.

class Swimmer:
    def move(self):
        return "nage"

class Flyer:
    def move(self):
        return "vole"

class Duck(Swimmer, Flyer):    # gauche gagne = Swimmer.move
    pass

class Goose(Flyer, Swimmer):   # inverse l'ordre = résultat différent
    pass

print(Duck().move())   # nage
print(Goose().move())  # vole
Le MRO parcourt (A, B, C) de gauche à droite
Duck().move()move surDuck ?non→ suivantoui→ exécutemove surSwimmer ?oui→ exécute
Pour Duck(Swimmer, Flyer), Python regarde Swimmer d'abord, puis Flyer. Si les deux définissent move, Swimmer.move gagne. Réordonne les parents et le résultat change.

Sur CPython, tu peux lire l'ordre réel via NomDeClasse.__mro__ (le runtime ici est MicroPython, qui n'expose pas l'attribut __mro__, donc le snippet ci-dessous est juste une référence pour ce que tu verrais en Python normal).

# Ce que CPython montrerait
class Swimmer:
    pass
class Flyer:
    pass
class Duck(Swimmer, Flyer):
    pass

for cls in Duck.__mro__:
    print(cls.__name__)
# Duck
# Swimmer
# Flyer
# object

C'est quoi cet object à la fin ?

Toutes les classes Python héritent finalement de la classe native object. Même quand tu ne l'écris pas, class Foo: est en interne class Foo(object):. C'est pour ça que __mro__ finit toujours par object.

Confirme avec une expérience rapide que l'ordre dans lequel tu listes les parents change le résultat.

① Dans class Swimmer:, définis move(self) qui retourne "nage". Fais pareil pour Flyer avec "vole".

② Définis class Duck(Swimmer, Flyer): et class Goose(Flyer, Swimmer):, tous les deux avec juste pass.

print le résultat de Duck().move() et Goose().move() — vois comment inverser l'ordre des parents inverse le résultat.

Éditeur Python

Exécuter le code pour voir le résultat

Héritage en diamant et linéarisation C3

Une autre forme qui revient est l'héritage en diamant : B et C héritent chacun de A, et D hérite à la fois de B et C — dessiner le graphe d'héritage donne une forme littérale de losange (diamond).

La forme de l'héritage en diamant
A(parent commun)Bhérite de AChérite de ADhérite de B et Chéritehéritehéritehérite
A est l'ancêtre commun. B et C héritent chacun de A. D hérite alors à la fois de B et C — les flèches forment un diamant.

Python calcule le MRO avec un algorithme appelé linéarisation C3, qui produit l'ordre intelligent « essayer d'abord les descendants (B, C), puis remonter à l'ancêtre commun (A) en dernier ».

class A:
    def method(self):
        return "A"

class B(A):
    def method(self):
        return "B"

class C(A):
    def method(self):
        return "C"

class D(B, C):       # diamant
    pass

print(D().method())  # B

Dans cet exemple, D().method() retourne "B" parce que le MRO est D → B → C → A. Si B ne définissait pas method, celle de C tournerait ; sans ça, celle de A. « D'abord à gauche, mais l'ancêtre commun en dernier » — c'est la règle pour l'héritage en diamant.

Appeler une méthode d'un parent spécifique par nom de classe

Quand tu veux spécifiquement appeler la méthode d'un parent particulier — pas juste celle qui gagne le MRO — super() ne te donne que la classe suivante dans le MRO, donc tu peux n'appeler qu'au plus une version parente. À la place, utilise ClasseParent.method(self, ...) pour choisir exactement celle que tu veux.

Comme l'appel passe par le côté classe, tu dois passer self toi-même comme premier argument — la seule particularité à retenir.

super() vs appel par nom de classe
C().hello()super().hello()ordre MRO(une seule)A.hello(self)B.hello(self)A et B tousappelables
super() exécute une seule méthode (la suivante dans le MRO). Appeler par nom de classe comme A.hello(self) te laisse cibler un parent spécifique indépendamment du MRO — pratique quand tu veux invoquer plusieurs méthodes parentes en séquence.
class A:
    def hello(self):
        print("hello depuis A")

class B:
    def hello(self):
        print("hello depuis B")

class C(A, B):
    def hello(self):
        A.hello(self)        # appeler explicitement le hello de A
        B.hello(self)        # appeler explicitement le hello de B
        print("hello depuis C")

C().hello()
# hello depuis A
# hello depuis B
# hello depuis C

Écris une classe C qui appelle les hello des deux parents par nom.

① Définis class A: avec hello(self) qui exécute print("hello depuis A").

② Définis class B: avec hello(self) qui exécute print("hello depuis B").

③ Définis class C(A, B): avec hello(self) qui appelle A.hello(self)B.hello(self)print("hello depuis C") dans cet ordre (n'oublie pas de passer self).

④ Lance C().hello() et confirme que trois lignes s'affichent.

Éditeur Python

Exécuter le code pour voir le résultat

Puissant, mais à utiliser avec parcimonie

L'héritage multiple est pratique, mais il faut garder le MRO en tête en permanence pour suivre le comportement — c'est un vrai coût cognitif. En pratique, plutôt que de forcer l'héritage multiple, utiliser un seul parent et garder de petites classes de fonctionnalités comme attributs (composition) est souvent plus clair. Garde l'héritage multiple pour des cas comme mixer des capacités indépendantes comme Swimmer / Flyer.

Ce que tu veuxComment l'écrire
Hériter de plusieurs parentsclass Enfant(A, B): ...
Voir quel parent gagneNomDeClasse.__mro__ (CPython)
Appeler seulement le suivant dans le MROsuper().method(...)
Cibler un parent spécifiqueClasseParent.method(self, ...)
QUIZ

Vérification des connaissances

Répondez à chaque question une par une.

Question 1Dans class Duck(Swimmer, Flyer):, si les deux parents définissent une méthode move, laquelle gagne ?

Question 2Quelle est la bonne façon de lire l'ordre de résolution des méthodes (MRO) d'une classe ? (CPython)

Question 3En héritage multiple, quelle est la bonne façon d'appeler la méthode d'un parent spécifique par son nom ?