W3docs

Héritage en Python

Apprenez l'héritage Python : sous-classes, surcharge de méthodes, super(), héritage multiple, MRO et isinstance — avec des exemples exécutables.

Comprendre l'héritage en Python

L'héritage vous permet de construire une nouvelle classe à partir d'une classe existante. La nouvelle classe (la sous-classe ou classe enfant) reçoit automatiquement tous les attributs et méthodes de la classe existante (la superclasse, classe de base ou classe parente). Vous pouvez ensuite ajouter de nouveaux comportements ou remplacer sélectivement ce que vous avez hérité.

Ce chapitre couvre :

  • La syntaxe pour créer des sous-classes
  • Comment super() fonctionne et pourquoi vous devriez l'utiliser
  • La surcharge de méthodes et l'appel de la version parente
  • L'héritage simple, multiniveau et multiple
  • L'ordre de résolution des méthodes (MRO) en Python
  • isinstance() et issubclass() pour les vérifications de type à l'exécution
  • Les pièges courants

Avant de lire ce chapitre, assurez-vous d'être à l'aise avec les classes et objets Python. Pour les autres piliers de la POO, consultez l'encapsulation Python et les classes abstraites Python.

La syntaxe de l'héritage

Pour créer une sous-classe, écrivez le nom de la classe parente entre parenthèses après le nom de la nouvelle classe :

class ParentClass:
    pass

class ChildClass(ParentClass):
    pass

Un exemple minimal mais concret avec une classe de base Vehicle et une sous-classe Car :

class Vehicle:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year

    def start(self):
        print(f"{self.make} {self.model} started.")

    def stop(self):
        print(f"{self.make} {self.model} stopped.")


class Car(Vehicle):
    def __init__(self, make, model, year, num_doors=4):
        super().__init__(make, model, year)   # delegate to Vehicle.__init__
        self.num_doors = num_doors


camry = Car("Toyota", "Camry", 2023)
camry.start()   # Toyota Camry started.
camry.stop()    # Toyota Camry stopped.
print(camry.num_doors)  # 4

Car hérite de start() et stop() de Vehicle sans les dupliquer. L'ajout de num_doors est la seule nouvelle préoccupation que Car doit gérer.

Utiliser super()

super() retourne un objet proxy qui délègue les appels de méthodes à la classe parente. Son utilisation la plus courante est à l'intérieur de __init__ pour initialiser les attributs du parent avant d'ajouter ceux de l'enfant :

class Car(Vehicle):
    def __init__(self, make, model, year, num_doors=4):
        super().__init__(make, model, year)  # runs Vehicle.__init__
        self.num_doors = num_doors

Pourquoi préférer super() à l'écriture directe de Vehicle.__init__(self, ...) ?

  • Maintenance : si vous renommez ou remplacez la classe parente, vous ne changez qu'une seule ligne.
  • Héritage multiple : super() suit l'ordre de résolution des méthodes (MRO) de Python, de sorte que chaque classe de la chaîne est appelée correctement. Coder en dur le nom du parent contourne cette logique.

Vous pouvez appeler super() pour n'importe quelle méthode, pas seulement __init__ :

class Car(Vehicle):
    def start(self):
        super().start()                               # call Vehicle.start first
        print(f"({self.num_doors}-door model ready)")

Surcharge de méthodes

Une sous-classe surcharge une méthode parente en définissant une méthode du même nom. Python appelle toujours la version la plus dérivée en premier :

class Vehicle:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year

    def start(self):
        print(f"{self.make} {self.model} started.")

    def stop(self):
        print(f"{self.make} {self.model} stopped.")


class Car(Vehicle):
    def start(self):
        print(f"{self.make} {self.model} revved the engine and started.")


camry = Car("Toyota", "Camry", 2023)
camry.start()  # Toyota Camry revved the engine and started.
camry.stop()   # Toyota Camry stopped.  (inherited, not overridden)

Lorsque vous souhaitez étendre plutôt que remplacer le comportement parent, appelez super() à l'intérieur de la surcharge :

class Car(Vehicle):
    def start(self):
        super().start()                         # Vehicle.start runs first
        print("Seatbelt reminder: buckle up!")  # then add the extra step

Héritage des attributs de classe

L'héritage s'applique également aux attributs de classe (partagés entre les instances). Une sous-classe peut les remplacer de la même manière qu'elle surcharge les méthodes :

class Vehicle:
    wheels = 4

class Motorcycle(Vehicle):
    wheels = 2   # override the class attribute

class Car(Vehicle):
    pass         # inherits wheels = 4


print(Motorcycle.wheels)  # 2
print(Car.wheels)         # 4
print(Vehicle.wheels)     # 4

Types d'héritage

Héritage simple

Un enfant, un parent — le schéma le plus courant.

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

    def speak(self):
        return f"{self.name} makes a sound."


class Dog(Animal):
    def speak(self):
        return f"{self.name} says woof!"


dog = Dog("Rex")
print(dog.speak())  # Rex says woof!

Héritage multiniveau

Une sous-classe peut elle-même être sous-classée, créant une chaîne :

class Vehicle:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year

    def stop(self):
        print(f"{self.make} {self.model} stopped.")


class Car(Vehicle):
    def __init__(self, make, model, year, num_doors=4):
        super().__init__(make, model, year)
        self.num_doors = num_doors

    def start(self):
        print(f"{self.make} {self.model} started.")


class ElectricCar(Car):
    def __init__(self, make, model, year, range_km):
        super().__init__(make, model, year)   # calls Car.__init__
        self.range_km = range_km

    def start(self):
        print(f"{self.make} {self.model} powered up silently.")

    def charge(self):
        print(f"Charging... range is {self.range_km} km.")


tesla = ElectricCar("Tesla", "Model 3", 2024, 580)
tesla.start()   # Tesla Model 3 powered up silently.
tesla.charge()  # Charging... range is 580 km.
tesla.stop()    # Tesla Model 3 stopped.  (inherited from Vehicle)

ElectricCar se trouve deux niveaux en dessous de Vehicle. Chaque appel à super().__init__ remonte d'un niveau dans la chaîne, de sorte que les trois méthodes __init__ sont exécutées.

Héritage multiple

Python permet à une classe d'hériter de plus d'un parent. Listez-les entre parenthèses, séparés par des virgules :

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

    def speak(self):
        return f"{self.name} makes a sound."


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


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


class Duck(Animal, Swimmer, Flyer):
    def speak(self):
        return f"{self.name} says quack!"


donald = Duck("Donald")
print(donald.speak())  # Donald says quack!
print(donald.swim())   # Donald swims.
print(donald.fly())    # Donald flies.

L'héritage multiple est puissant mais introduit de la complexité. Utilisez des mixins — de petites classes à usage unique qui ajoutent une seule capacité — pour le rendre gérable. Swimmer et Flyer ci-dessus illustrent exactement ce schéma.

L'ordre de résolution des méthodes (MRO)

Lorsque Python recherche une méthode ou un attribut, il suit un ordre de recherche déterministe appelé l'ordre de résolution des méthodes (MRO). Pour l'héritage simple, l'ordre est évident (enfant → parent → grand-parent → object). Pour l'héritage multiple, Python utilise l'algorithme de linéarisation C3 pour produire un ordre non ambigu.

Inspectez le MRO de n'importe quelle classe avec .__mro__ ou help() :

print(Duck.__mro__)
# (<class 'Duck'>, <class 'Animal'>, <class 'Swimmer'>, <class 'Flyer'>, <class 'object'>)

Le MRO est le plus important lorsque plusieurs parents définissent la même méthode. Python choisit la première classe dans le MRO qui la définit. C'est pourquoi super() est important dans les hiérarchies à héritage multiple : chaque classe du MRO appelle super(), de sorte que chaque classe de la chaîne a la possibilité de s'exécuter.

class Base:
    def greet(self):
        print("Hello from Base")


class Left(Base):
    def greet(self):
        print("Hello from Left")
        super().greet()


class Right(Base):
    def greet(self):
        print("Hello from Right")
        super().greet()


class Child(Left, Right):
    def greet(self):
        print("Hello from Child")
        super().greet()


Child().greet()
# Hello from Child
# Hello from Left
# Hello from Right
# Hello from Base

Chaque appel à super() suit le MRO (Child → Left → Right → Base), de sorte que chaque greet() s'exécute exactement une fois, même si Base apparaît comme parent de Left et de Right. C'est le problème du diamant — le MRO de Python le résout proprement.

Vérifier l'héritage à l'exécution

isinstance(obj, cls)

Retourne True si obj est une instance de cls ou de l'une de ses sous-classes :

tesla = ElectricCar("Tesla", "Model 3", 2024, 580)

print(isinstance(tesla, ElectricCar))  # True
print(isinstance(tesla, Car))          # True  — Car is a parent
print(isinstance(tesla, Vehicle))      # True  — Vehicle is a grandparent
print(isinstance(tesla, str))          # False

C'est plus fiable que de comparer type(obj) == Car, ce qui retourne False pour les sous-classes.

issubclass(sub, cls)

Retourne True si sub est une sous-classe de cls (y compris elle-même) :

print(issubclass(ElectricCar, Vehicle))  # True
print(issubclass(Car, ElectricCar))      # False
print(issubclass(Car, Car))              # True  — a class is a subclass of itself

Pièges courants

Oublier d'appeler super().__init__()

Si le __init__ de l'enfant n'appelle pas super().__init__(), les attributs du parent ne sont jamais définis. Toute méthode qui les attend lèvera une AttributeError :

class Car(Vehicle):
    def __init__(self, make, model, year, num_doors=4):
        # forgot super().__init__(...)
        self.num_doors = num_doors

c = Car("Toyota", "Camry", 2023)
c.start()  # AttributeError: 'Car' object has no attribute 'make'

Surcharger sans appeler super() quand on voulait étendre

Si vous surchargez une méthode et oubliez super(), la version parente ne s'exécute jamais. Cela supprime silencieusement un comportement dont d'autres parties du code peuvent dépendre.

Hiérarchies d'héritage multiple trop profondes

Plus de deux niveaux d'héritage multiple est très difficile à raisonner. Si vous vous retrouvez à écrire class Foo(A, B, C, D), envisagez d'utiliser la composition — stocker des instances comme attributs — à la place.

Avantages de l'héritage

  1. Réutilisation du code — la logique partagée se trouve en un seul endroit ; les sous-classes en héritent gratuitement.
  2. Extensibilité — vous pouvez ajouter ou modifier un comportement dans une sous-classe sans toucher au parent, ce qui laisse les appelants existants inchangés.
  3. Polymorphisme — les fonctions qui acceptent un Vehicle fonctionnent aussi bien avec n'importe quel Car, Motorcycle ou ElectricCar. Consultez le polymorphisme Python pour une vue complète.

Pratique

Pratique
Which of the following statements about Python inheritance are correct according to the information provided on the specified URL?
Which of the following statements about Python inheritance are correct according to the information provided on the specified URL?
Was this page helpful?