Accéder au contenu principal

L'héritage Python : Meilleures pratiques pour un code réutilisable

L'héritage Python vous permet de construire de nouvelles classes en réutilisant et en étendant les fonctionnalités des classes existantes. Apprenez à concevoir des relations de classe parent-enfant, à mettre en œuvre des modèles d'héritage et à appliquer des techniques telles que la superposition de méthodes.
Actualisé 12 févr. 2025  · 9 min de lecture

Imaginez que vous construisiez un système logiciel avec plusieurs rôles d'utilisateurs comme les étudiants, les enseignants et les administrateurs. Ces rôles partagent des attributs communs tels que le nom et l'identifiant, mais ils requièrent également des fonctionnalités qui leur sont propres. Au lieu de dupliquer le code, l'héritage vous permet de définir un comportement partagé dans une classe mère et de l'étendre dans des classes enfants spécialisées.

Dans cet article, nous allons explorer l'héritage Python, en abordant des concepts de base et avancés tels que la superposition de méthodes et la fonction super(), qui est une fonction intégrée qui renvoie un objet temporaire de la superclasse, , afin que vous puissiez accéder à ses méthodes sans nommer explicitement la classe parente. Ne vous inquiétez pas si cela n'a pas encore de sens, car nous en apprendrons plus à ce sujet ci-dessous.

Apprenez Python à partir de zéro

Maîtrisez Python pour la science des données et acquérez des compétences recherchées.
Commencez à apprendre gratuitement

Les bases de l'héritage en Python

L'héritage est l'un des piliers fondamentaux de la programmation orientée objet (POO) qui permet à une classe (appelée classe enfant) de dériver des attributs et des méthodes d'une autre classe (appelée classe mère). Cette caractéristique est essentielle pour laréutilisation du code sur le site et simplifie la maintenance, ce qui facilite la création de programmes évolutifs et efficaces.

Définition des classes parents et enfants

Avant d'aller plus loin, explorons la relation entre les classes parent et enfant.

Classe mère

Commençons par la classe mère. Une classe mère est la classe de base dont dérivent les classes enfants. Il encapsule les attributs et les méthodes partagés.

En utilisant Python, voici comment nous définissons une classe mère :

class ParentClass:
    def __init__(self, attributes):
        # Initialize attributes
        pass

    def method(self):
        # Define behavior
        pass

Classe d'enfants 

Une classe enfant hérite des attributs et des méthodes de la classe parentale. Cela lui permet d'utiliser les fonctionnalités définies dans la classe mère. Le code suivant montre comment une classe enfant hérite des attributs et des méthodes d'une classe parentale :

class ChildClass(ParentClass):
    def additional_method(self):
        # Define new behavior
        pass

Cette syntaxe simple permet à la classe enfant d'utiliser et d'étendre les fonctionnalités définies dans la classe parent.

Création d'une classe mère et d'une classe enfant

Créons un exemple pratique avec une classe Person comme parent et une classe Student comme enfant.

Création de la classe mère

La classe Person contient des attributs partagés et une méthode d'affichage des informations :    

# Defining the Parent Class
class Person:
    def __init__(self, name, id):
        self.name = name
        self.id = id
    def display_info(self):
        return f"Name: {self.name}, ID: {self.id}"

Création de la classe enfantine

La classe Student hérite de Person et ajoute une nouvelle méthode study.

# Defining the Child Class
class Student(Person):
    def study(self):
        return f"{self.name} is studying."

Testons les classes parent et enfant.

# Creating and Testing Instances
student = Student("Samuel", 102)
print(student.display_info())  
print(student.study())      
Name: Samuel, ID: 102
Samuel is studying.

Voici ce qui se passe :

  1. La classe Student utilise la méthode __init__ de Person pour initialiser name et id.

  2. La méthode study est propre à la classe Student, dont elle étend les fonctionnalités.

  3. La méthode display_info est héritée directement de Person.

Types d'héritage en Python

L'héritage en Python permet aux classes d'hériter des attributs et des comportements d'autres classes, ce qui favorise la réutilisation du code et une conception propre, comme nous l'avons évoqué précédemment. Dans cette section, nous pouvons parler des différents types d'héritage Python, qui comprennent l'héritage simple, multiple, hiérarchique et hybride en tant que catégories distinctes.

Héritage unique

On parle d'héritage unique lorsqu'une classe enfant hérite d'une seule classe parentale, ce qui lui permet d'étendre les fonctionnalités de la classe parentale. Ceci est utile lorsqu'un type d'objet partage des propriétés communes avec une catégorie plus large, mais nécessite également des attributs ou des comportements supplémentaires.

L'exemple que j'ai commencé à étudier tout à l'heure concernait l'héritage simple, mais examinons-le de plus près : Dans un système de gestion scolaire, tous les individus, y compris les élèves, les enseignants et le personnel, partagent des informations communes telles que name et ID. Cependant, les étudiants ont également des dossiers académiques tels que les notes et les cours inscrits. En utilisant l'héritage simple, nous pouvons créer une classe Person pour les attributs partagés et l'étendre avec une classeStudent pour les détails académiques.

héritage unique

Héritage unique. Image par l'auteur

Voici un bon exemple du scénario ci-dessus :

class Person:
    """Represents a general person with basic details."""
    def __init__(self, name, id):
        self.name = name
        self.id = id

    def get_details(self):
        return f"Name: {self.name}, ID: {self.id}"

class Student(Person):
    """Represents a student, extending the Person class to include academic details."""
    def __init__(self, name, id, grade, courses):
        super().__init__(name, id)
        self.grade = grade
        self.courses = courses

    def get_details(self):
        return f"Name: {self.name}, ID: {self.id}, Grade: {self.grade}, Courses: {', '.join(self.courses)}"

# Example usage in a school system
student = Student("Samuel", 5678, "B+", ["Math", "Physics", "Computer Science"])
print(student.get_details())
Name: Samuel, ID: 5678, Grade: B+, Courses: Math, Physics, Computer Science

La classe Student hérite de la méthode get_details() de Person mais l'étend pour inclure grade et courses. C'est un bon exemple de la façon dont l'héritage unique favorise ce que l'on appelle le code modulaire.

Héritage multiple

L'héritage multiple, comme un arbre généalogique, permet à une classe enfant d'hériter de plus d'une classe parentale, en combinant les attributs et les comportements de chacune d'elles. Cela peut entraîner des conflits potentiels, que Python résout à l'aide d'un ordre de résolution des méthodes (MRO).

héritage multiple

Héritage multiple. Image par l'auteur

Jetez un coup d'œil :

class Person:
    def get_details(self):
        return "Details of a person."

class Athlete:
    def get_skill(self):
        return "Athletic skills."

class Student(Person, Athlete):
    pass

# Example usage
student = Student()
print(student.get_details())
print(student.get_skill())
Details of a person.
Athletic skills.

Nous constatons que la classe Student a hérité des attributs et des méthodes des classes Person et Athlete. Sans aucun effort supplémentaire, la classe Student a accès à la méthode get_details() de la classe mère Person et à la méthode get_skill() de la classe mère Athlete. Nous combinons effectivement des fonctionnalités provenant de sources multiples.

Cependant, le fait d'hériter de plusieurs classes peut entraîner des conflits. Que se passe-t-il si les deux classes mères définissent une méthode ou un attribut portant le même nom ? J'ai parlé tout à l'heure de l'ordre de résolution des méthodes, mais permettez-moi d'en dire un peu plus à ce sujet. Ordre de résolution des méthodes détermine l'ordre dans lequel les classes sont recherchées pour les méthodes et les attributs. Le MRO suit une approche en profondeur, de gauche à droite.

Vous pouvez visualiser le MRO d'une classe en utilisant l'attribut __mro__ ou la méthode mro():

print(Student.__mro__)
(<class '__main__.Student'>, <class '__main__.Person'>, <class '__main__.Athlete'>, <class 'object'>)

Héritage multiniveau, hiérarchique et hybride

Python prend également en charge des structures d'héritage plus complexes. Je montrerai ces idées plus complexes en utilisant le même exemple.

Héritage à plusieurs niveaux

L'héritage à plusieurs niveaux se produit lorsqu'une classe enfant hérite d'une autre classe enfant, et que cette dernière hérite d'une classe parentale. Cela crée une chaîne d'héritage.

héritage à plusieurs niveaux

Héritage à plusieurs niveaux. Image par l'auteur

En voici un bon exemple :

class Person:
    def __init__(self, name, id):
        self.name = name
        self.id = id

    def get_details(self):
        return f"Name: {self.name}, ID: {self.id}"

class Student(Person):
    def __init__(self, name, id, grade):
        super().__init__(name, id)
        self.grade = grade

    def get_details(self):
        return f"Name: {self.name}, ID: {self.id}, Grade: {self.grade}"

class GraduateStudent(Student):
    def __init__(self, name, id, grade, thesis_title):
        super().__init__(name, id, grade)
        self.thesis_title = thesis_title

    def get_details(self):
        return f"Name: {self.name}, ID: {self.id}, Grade: {self.grade}, Thesis: {self.thesis_title}"

# Example usage
grad_student = GraduateStudent("Charlie", 91011, "A", "AI in Healthcare")
print(grad_student.get_details())
Name: Charlie, ID: 91011, Grade: A, Thesis: AI in Healthcare

Ici, chaque classe de la chaîne ajoute quelque chose de nouveau : Person gère les noms et les identifiants, Student inclut les notes et GraduateStudent introduit une thèse. Grâce à super().__init__(), nous réutilisons la logique d'initialisation sans dupliquer le code. C'est efficace, soigné et cela garantit que chaque niveau de "l'échelle de l'héritage", comme je l'entends, fonctionne.

Héritage hiérarchique

Dans l'héritage hiérarchique, plusieurs classes enfants héritent d'une seule classe parentale, ce qui permet de partager le comportement de sous-classes dotées d'attributs uniques.

héritage hiérarchique

Héritage hiérarchique. Image par l'auteur

Voyons ensemble un bon exemple :

class Person:
    def __init__(self, name, id):
        self.name = name
        self.id = id

    def get_details(self):
        return f"Name: {self.name}, ID: {self.id}"

class Student(Person):
    def __init__(self, name, id, grade):
        super().__init__(name, id)
        self.grade = grade

    def get_details(self):
        return f"Name: {self.name}, ID: {self.id}, Grade: {self.grade}"

class Teacher(Person):
    def __init__(self, name, id, subject):
        super().__init__(name, id)
        self.subject = subject

    def get_details(self):
        return f"Name: {self.name}, ID: {self.id}, Subject: {self.subject}"

# Example usage
student = Student("Samuel", 5678, "B+")
teacher = Teacher("Dr. Smith", 1234, "Math")
print(student.get_details())  
print(teacher.get_details())
Name: Samuel, ID: 5678, Grade: B+
Dr. Smith, ID: 1234, Subject: Math

Ici, la classe Person sert de base, offrant des attributs et des méthodes communs (name, id, et get_details). Les classes Student et Teacher étendent ensuite cette fonctionnalité en ajoutant leurs propres propriétés (grade et subject) et en personnalisant la méthode get_details pour refléter leurs contextes spécifiques.

Avec cette approche, les fonctionnalités partagées restent en un seul endroit (la classe Person ), tandis que les comportements spécialisés sont soigneusement encapsulés dans les sous-classes.

Héritage hybride

L'héritage hybride combine plusieurs types d'héritage, tels que l'héritage à plusieurs niveaux ou l'héritage multiple, pour modéliser des relations plus complexes.

héritage hybride

Héritage hybride. Image par l'auteur

Prenons un exemple qui illustre la complexité de l'héritage hybride.

# Base class
class Person:
    def __init__(self, name, id):
        self.name = name
        self.id = id

    def get_details(self):
        return f"Name: {self.name}, ID: {self.id}"

# Intermediate class inheriting from the base class
class Employee(Person):
    def __init__(self, name, id, position):
        super().__init__(name, id)
        self.position = position

    def get_position(self):
        return f"Position: {self.position}"

# Another independent base class
class Athlete:
    def __init__(self, sport):
        self.sport = sport

    def get_sport(self):
        return f"Sport: {self.sport}"

# Derived class combining Employee and Athlete
class Student(Employee, Athlete):
    def __init__(self, name, id, position, grade, sport):
        Employee.__init__(self, name, id, position)
        Athlete.__init__(self, sport)
        self.grade = grade

    def get_grade(self):
        return f"Grade: {self.grade}"

# Example usage
student = Student("Samuel", 1234, "Intern", "A", "Soccer")
print(student.get_details())  # From Person
print(student.get_position())  # From Employee
print(student.get_grade())  # From Student
print(student.get_sport())  # From Athlete
Name: Samuel, ID: 1234
Position: Intern
Grade: A
Sport: Soccer

Dans cet exemple, la classe Student démontre l'héritage hybride en héritant des attributs et des méthodes de Employee (qui hérite elle-même de Person) et de Athlete. Il combine l'héritage hiérarchique de (où Employee hérite de Person) et l'héritage multiple (où Student hérite à la fois de Employee et de Athlete ).

Avantages de l'héritage en Python

Il est maintenant temps de voir les forces et les faiblesses : 

Avantages de l'héritage

  1. Réutilisation : Avec l'héritage, vous pouvez écrire du code une fois dans la classe mère et le réutiliser dans les classes enfants. Si l'on reprend l'exemple, FullTimeEmployee et Contractor peuvent hériter d'une méthode get_details() de la classe mère Employee.

  2. Simplicité : L'héritage permet de modéliser clairement les relations. Un bon exemple est la classe FullTimeEmployee qui est un type de la classe parentale Employee.

  3. Évolutivité : Il permet également d'ajouter de nouvelles fonctionnalités ou classes enfantines sans affecter le code existant. Par exemple, nous pouvons facilement ajouter une nouvelle classe Intern en tant que classe enfant.

Limites potentielles de l'héritage

  1. La complexité : Cela ne vous surprendra pas, mais trop de niveaux d'héritage peuvent rendre le code difficile à suivre. Par exemple, si une classe Employee a trop de classes enfants comme Manager, Engineer, Intern, etc., elle peut devenir confuse.

  2. Dépendance : Les modifications apportées à une classe mère peuvent affecter involontairement toutes les sous-classes. Si vous modifiez Employee par exemple, vous risquez de casser FullTimeEmployee ou Contractor.

  3. Mauvais usage : L'utilisation de l'héritage lorsqu'il n'est pas le mieux adapté peut compliquer les conceptions. Vous ne voudriez pas créer une solution où Car hérite de Boat juste pour réutiliser move(). La relation n'a pas de sens.

Techniques avancées d'héritage en Python

Maintenant que nous avons exploré les bases de l'héritage, examinons quelques techniques avancées. Ces techniques, telles que la superposition de méthodes, super(), les classes de base abstraites et le polymorphisme, améliorent la flexibilité du code et permettent des schémas de conception plus sophistiqués. 

Surcharge de méthodes en Python

La surcharge de méthode permet à une classe enfant de fournir une implémentation spécifique pour une méthode déjà définie dans sa classe mère. Ceci est utile lorsque le comportement hérité ne répond pas entièrement aux exigences de la classe enfantine.

class Person:
    def __init__(self, name, id):
        self.name = name
        self.id = id
    
    def get_details(self):
        return f"Name: {self.name}, ID: {self.id}"

class Student(Person):
    def __init__(self, name, id, grade):
        super().__init__(name, id)
        self.grade = grade
    
    # Overriding the get_details method
    def get_details(self):
        return f"Name: {self.name}, ID: {self.id}, Grade: {self.grade}"

# Example usage
student = Student("Samuel", 1234, "A")
print(student.get_details())
Name: Samuel, ID: 1234, Grade: A

Ici, la classe Student remplace la méthode get_details() de la classe Person pour donner ses propres implémentations spécifiques. Cela permet à la classe enfant d'avoir son propre comportement tout en suivant le même nom de méthode.

Alors pourquoi passer outre ? Nous surchargeons parce que nous voulons personnaliser le comportement hérité et aussi parce que nous voulons adapter la fonctionnalité d'une méthode parent aux exigences uniques d'une classe enfant.

Utilisation de super() pour l'initialisation du parent

La fonction super() est utilisée pour appeler les méthodes de la classe mère à partir de la classe enfant. Ceci est particulièrement utile lorsque vous souhaitez étendre ou modifier la fonctionnalité d'une méthode de la classe parente, telle que la méthode du constructeur __Init__().

Pourquoi utiliser la fonction super()? Nous utilisons la fonction super parce que nous voulons appeler et initialiser le constructeur de la classe parente et aussi parce que nous voulons éviter de nommer explicitement la classe parente. Cela est utile, en particulier dans les cas d'héritages multiples.

class Person:
    def __init__(self, name, id):
        self.name = name
        self.id = id

class Student(Person):
    def __init__(self, name, id, grade):
        # Using super() to initialize the parent class
        super().__init__(name, id)
        self.grade = grade

# Example usage
student = Student("Samuel", 5678, "B+")
print(student.name)
print(student.id)
print(student.grade)
Samuel
5678
B+

Ici, la classe Student utilise super().__init__(name, id) pour appeler la méthode __init__ de la classe parente Person, de sorte qu'il n'est pas nécessaire de répéter le code pour initialiser les attributs name et id. La classe enfant introduit ensuite un attribut grade, qui est spécifique à la classe Student.

Classes de base abstraites (ABC)

Une classe de base abstraite (ABC) est une classe qui ne peut pas être utilisée directement pour créer des objets. Il a pour but de définir un ensemble commun de méthodes que d'autres classes doivent mettre en œuvre. Les ABC sont donc utiles lorsque vous voulez vous assurer que certaines méthodes sont toujours présentes dans les classes enfantines.

from abc import ABC, abstractmethod

class Person(ABC):
    @abstractmethod
    def get_details(self):
        pass

class Student(Person):
    def __init__(self, name, id, grade):
        self.name = name
        self.id = id
        self.grade = grade
    
    def get_details(self):
        return f"Name: {self.name}, ID: {self.id}, Grade: {self.grade}"

# Example usage
student = Student("Hamilton", 7890, "A-")
print(student.get_details())
Name: Hamilton, ID: 7890, Grade: A-

La classe Person est une classe abstraite qui exige que toute classe enfant implémente la méthode get_details(). Cette méthode est ensuite mise en œuvre par la classe enfant, Student.

Polymorphisme

Le polymorphisme est synonyme de formes multiples. En Python, cela permet à différentes classes d'utiliser le même nom de méthode, mais chacune peut implémenter cette méthode d'une manière différente.

Le polymorphisme nous aide à écrire un code qui peut fonctionner avec des objets de différentes classes, même si ces classes ont des comportements différents :

class Person:
    def get_details(self):
        return "Details of a person."

class Student(Person):
    def get_details(self):
        return "Details of a student."

class Teacher(Person):
    def get_details(self):
        return "Details of a teacher."

# Example usage
def print_details(person):
    print(person.get_details())

student = Student()
teacher = Teacher()

print_details(student) 
print_details(teacher) 
Details of a student.
Details of a teacher.

Dans cet exemple, la fonction print_details() peut accepter n'importe quel objet du type Person, mais elle appellera la méthode get_details() appropriée selon que l'objet est un Student ou un Teacher.

Erreurs courantes et meilleures pratiques

Bien que l'héritage soit puissant, il est facile de l'utiliser à mauvais escient. Je vais partager avec vous quelques idées pour vous aider à en tirer le meilleur parti.

Éviter les surprises avec les méthodes surchargées

Lorsqu'une classe enfant remplace une méthode de sa classe mère, le comportement peut changer.

Par exemple, si la classe mère Employee possède une méthode calculate_pay() et que la classe enfant Manager la remplace sans tenir compte de tous les scénarios, les calculs de rémunération risquent d'être incorrects.

Dans ce cas, la meilleure pratique consiste à toujours tester minutieusement les méthodes remplacées et à documenter leur comportement.

Choisir entre l'héritage et la composition

Je sais que cet article traite de l'héritage, mais ce n'est pas toujours la bonne approche. Parfois, la composition, qui consiste à créer des classes en combinant des objets plutôt qu'en les étendant, peut être mieux adaptée à ce que vous faites.

Pour schématiser les différences de la manière la plus élémentaire, pensez que.. :

  • L'héritage fait référence à relations "Is-a". Par exemple, un Manager est un Employee.

  • La composition fait référence à relations "Has-a". Par exemple, un Car a un Engine.

Alors, comment savoir si la composition est la meilleure approche à adopter ? Utilisez la composition lorsquela relation n'est pas strictement hiérarchique et/ou lorsque vous souhaitezréduire le couplage étroit entre les classes.

Nous pourrions également dire que, tandis que l'héritage modélise les relations, la composition se concentre sur la fonctionnalité. Pour vous aider, considérez ceci :

  • Utilisez l'héritage lorsque les objets sont naturellement hiérarchisés. Par exemple, Animal > Bird > Parrot.

  • Utilisez la composition lorsque des objets partagent des fonctionnalités mais ne sont pas liés. Par exemple, les sites Printer et Scanner utilisent tous deux le site DeviceManager.

Éviter les chaînes d'héritage profondes

Les chaînes d'héritage profondes (plusieurs niveaux de relations parent-enfant) peuvent rendre votre code difficile à lire et à maintenir. C'est un problème car les changements apportés à une classe mère peuvent affecter involontairement de nombreuses classes enfants. En outre, d ebuggingdevient complexe lorsque le comportement est réparti sur plusieurs niveaux.

Dans ce cas, la meilleure pratique consiste à kgarder les hiérarchies peu profondes. De même, consider à utiliser la composition (comme je l'ai mentionné plus haut) ou à diviser une chaîne en hiérarchies distinctes si vous trouvez qu'elle devient trop profonde.

Conclusion

L'héritage est un pilier majeur de la programmation orientée objet qui permet aux développeurs comme vous de créer un code réutilisable, modulaire et évolutif. Si vous maîtrisez l'héritage, vous pourrez facilement simplifier des systèmes complexes.

Un bon moyen d'approfondir votre compréhension est d'essayer de construire des structures d'héritage dans vos projets. Commencez simplement, puis expérimentez des hiérarchies plus complexes pour voir comment elles fonctionnent dans la pratique.

Si vous souhaitez aller encore plus loin, vous pouvez consulter notre cours Concepts du paradigme de programmation pour une compréhension plus approfondie de l'héritage et d'autres idées. Notre cursus professionnel de développeur Python est également une bonne ressource qui offre un parcours complet pour développer des compétences de programmation avancées qui vous équiperont pour le développement de logiciels.


Samuel Shaibu's photo
Author
Samuel Shaibu
LinkedIn

Professionnel expérimenté des données et écrivain passionné par l'autonomisation des experts en herbe dans le domaine des données.

FAQ sur l'héritage Python

Qu'est-ce que l'héritage en Python ?

En Python, l'héritage est un mécanisme qui permet à une classe d'hériter des attributs et des méthodes d'une autre classe, ce qui favorise la réutilisation du code et les structures de classe hiérarchiques.

Comment fonctionne l'héritage Python ?

L'héritage Python consiste à définir une nouvelle classe qui hérite des attributs et des méthodes d'une classe existante, ce qui permet de créer des structures de classe hiérarchiques.

Quels sont les types d'héritage en Python ?

Python prend en charge plusieurs types d'héritage, notamment l'héritage simple, multiple, multiniveau et hiérarchique.

Quelle est la différence entre l'héritage simple et l'héritage multiple en Python ?

L'héritage simple ne concerne qu'une seule classe mère, tandis que l'héritage multiple permet à une classe d'hériter de plusieurs classes mères.

Comment implémenter l'héritage en Python ?

Mettez en œuvre l'héritage en définissant une nouvelle classe qui spécifie une classe mère dans sa définition, à l'aide de la syntaxe class ChildClass(ParentClass):.

Quels sont les avantages de l'héritage en Python ?

L'héritage favorise la réutilisation du code, permet la création de structures de classes hiérarchiques et prend en charge le polymorphisme, ce qui rend le code plus flexible et plus facile à maintenir.

Comment implémenter l'héritage multiple en Python ?

L'héritage multiple est mis en œuvre en définissant une classe qui hérite de plusieurs classes de base, Python gérant la résolution des méthodes en utilisant l'ordre de résolution des méthodes (MRO).

Sujets