Comprendre l'héritage de classe en Python 3
Introduction
La programmation orientée objet crée des modèles de code réutilisables pour réduire la redondance dans les projets de développement. Une des façons dont la programmation orientée objet permet d'obtenir du code recyclable est l'héritage, lorsque une sous-classe peut tirer parti du code d'une autre classe de base.
Ce tutoriel passera en revue certains des principaux aspects de l'héritage en Python, y compris le fonctionnement des classes parentes et des classes enfants, comment redéfinir des méthodes et des attributs, comment utiliser la fonction super(), et comment tirer parti de l'héritage multiple.
Conditions préalables
Vous devez avoir Python 3 installé et un environnement de programmation configuré sur votre ordinateur ou serveur. Si vous n'avez pas d'environnement de programmation configuré, vous pouvez consulter les guides d'installation et de configuration pour un environnement de programmation local ou pour un environnement de programmation sur votre serveur, approprié à votre système d'exploitation (Ubuntu, CentOS, Debian, etc.).
Qu'est-ce que l'héritage ?
L'héritage est lorsque qu'une classe utilise du code construit dans une autre classe. Si nous pensons à l'héritage en termes de biologie, nous pouvons penser à un enfant héritant de certains traits de ses parents. C'est-à-dire qu'un enfant peut hériter de la taille ou de la couleur des yeux d'un parent. Les enfants peuvent également partager le même nom de famille que leurs parents.
Les classes appelées classes enfants ou sous-classes héritent des méthodes et des variables des classes parentes ou classes de base.
Nous pouvons penser à une classe parente appelée Parent qui a des variables de classe pour nom_de_famille, hauteur et couleur_des_yeux que la classe enfant Child héritera de la Parent.
Parce que la sous-classe Child hérite de la classe de base Parent, la classe Child peut réutiliser le code de Parent, permettant au programmeur d'utiliser moins de lignes de code et de réduire la redondance.
Cours pour parents
Les classes parent ou de base créent un modèle à partir duquel des classes enfants ou des sous-classes peuvent être basées. Les classes parent nous permettent de créer des classes enfants par héritage sans avoir à réécrire le même code à chaque fois. Toute classe peut être transformée en classe parent, donc elles sont toutes des classes pleinement fonctionnelles à part entière, plutôt que de simples modèles.
Disons que nous avons une classe parente générale Bank_account qui a des classes enfants Personal_account et Business_account. Beaucoup des méthodes entre les comptes personnels et les comptes d'entreprise seront similaires, telles que les méthodes pour retirer et déposer de l'argent, donc celles-ci peuvent appartenir à la classe parente de Bank_account. La sous-classe Business_account aurait des méthodes spécifiques à elle, y compris peut-être une façon de collecter des dossiers et des formulaires d'entreprise, ainsi qu'une variable employee_identification_number.
De même, une classe Animal peut avoir des méthodes eating() et sleeping(), et une sous-classe Snake peut inclure ses propres méthodes spécifiques hissing() et slithering().
Créons une classe parent Fish que nous utiliserons plus tard pour construire des types de poissons en tant que sous-classes. Chacun de ces poissons aura des prénoms et des noms de famille en plus de caractéristiques.
Info : Pour suivre l'exemple de code dans ce tutoriel, ouvrez une interface Python interactive sur votre système local en exécutant la commande python3. Ensuite, vous pouvez copier, coller ou modifier les exemples en les ajoutant après l'invite >>>.
Nous allons créer un nouveau fichier appelé fish.py et commencer avec la méthode de constructeur __init__(), que nous remplirons avec les variables de classe first_name et last_name pour chaque objet ou sous-classe Fish.
class Fish:
def __init__(self, first_name, last_name="Fish"):
self.first_name = first_name
self.last_name = last_name
Nous avons initialisé notre variable last_name avec la chaîne "Fish" car nous savons que la plupart des poissons auront ceci comme nom de famille.
Ajoutons également quelques autres méthodes :
class Fish:
def __init__(self, first_name, last_name="Fish"):
self.first_name = first_name
self.last_name = last_name
def swim(self):
print("The fish is swimming.")
def swim_backwards(self):
print("The fish can swim backwards.")
Nous avons ajouté les méthodes nager() et nager_en_arrière() à la classe Poisson, afin que chaque sous-classe puisse également utiliser ces méthodes.
Étant donné que la plupart des poissons que nous allons créer sont considérés comme des poissons osseux (c'est-à-dire qu'ils ont un squelette en os) plutôt que des poissons cartilagineux (c'est-à-dire qu'ils ont un squelette en cartilage), nous pouvons ajouter quelques attributs supplémentaires à la méthode __init__() :
class Fish:
def __init__(self, first_name, last_name="Fish",
skeleton="bone", eyelids=False):
self.first_name = first_name
self.last_name = last_name
self.skeleton = skeleton
self.eyelids = eyelids
def swim(self):
print("The fish is swimming.")
def swim_backwards(self):
print("The fish can swim backwards.")
La construction d'une classe parente suit la même méthodologie que la construction de toute autre classe, sauf que nous réfléchissons à quelles méthodes les classes enfants pourront utiliser une fois que nous les aurons créées.
Cours pour enfants
Les classes enfant ou sous-classes sont des classes qui hériteront de la classe parente. Cela signifie que chaque classe enfant pourra utiliser les méthodes et les variables de la classe parente.
Par exemple, une classe enfant Goldfish qui sous-classe la classe Fish pourra utiliser la méthode swim() déclarée dans Fish sans avoir besoin de la déclarer.
Nous pouvons considérer chaque classe d'enfant comme étant une classe de la classe parente. C'est-à-dire que si nous avons une classe d'enfant appelée Losange et une classe parente appelée Parallélogramme, nous pouvons dire qu'un Losange est un Parallélogramme, tout comme un Poisson rouge est un Poisson.
La première ligne d'une classe enfant ressemble un peu différente de celle des classes non-enfant car vous devez passer la classe parente dans la classe enfant en tant que paramètre :
class Trout(Fish):
La classe Trout est une sous-classe de la classe Fish. Nous le savons en raison de l'inclusion du mot Fish entre parenthèses.
Avec les classes enfants, nous pouvons choisir d'ajouter plus de méthodes, de remplacer les méthodes parent existantes, ou d'accepter les méthodes parent par défaut avec le mot-clé pass, ce que nous ferons dans ce cas :
...
class Trout(Fish):
pass
Nous pouvons maintenant créer un objet Trout sans avoir à définir des méthodes supplémentaires.
...
class Trout(Fish):
pass
terry = Trout("Terry")
print(terry.first_name + " " + terry.last_name)
print(terry.skeleton)
print(terry.eyelids)
terry.swim()
terry.swim_backwards()
Nous avons créé un objet Trout terry qui utilise chacune des méthodes de la classe Fish même si nous n'avons pas défini ces méthodes dans la classe enfant Trout. Nous n'avons eu besoin que de passer la valeur de "Terry" à la variable first_name parce que toutes les autres variables avaient été initialisées.
Lorsque nous exécutons le programme, nous recevons la sortie suivante :
Terry Fish
bone
False
The fish is swimming.
The fish can swim backwards.
Ensuite, créons une autre classe enfant qui inclut sa propre méthode. Nous appellerons cette classe Clownfish, et sa méthode spéciale lui permettra de vivre avec des anémones de mer :
...
class Clownfish(Fish):
def live_with_anemone(self):
print("The clownfish is coexisting with sea anemone.")
Ensuite, créons un objet Clownfish pour voir comment cela fonctionne :
...
casey = Clownfish("Casey")
print(casey.first_name + " " + casey.last_name)
casey.swim()
casey.live_with_anemone()
Lorsque nous exécutons le programme, nous recevons la sortie suivante :
Casey Fish
The fish is swimming.
The clownfish is coexisting with sea anemone.
La sortie montre que l'objet Clownfish casey est capable d'utiliser les méthodes Fish __init__() et swim() ainsi que sa méthode de classe enfant live_with_anemone().
Si nous essayons d'utiliser la méthode live_with_anemone() dans un objet Trout, nous obtiendrons une erreur :
terry.live_with_anemone()
AttributeError: 'Trout' object has no attribute 'live_with_anemone'
C'est parce que la méthode live_with_anemone() appartient uniquement à la sous-classe Clownfish, et non à la classe parente Fish.
Les classes enfants héritent des méthodes de la classe parente à laquelle elles appartiennent, de sorte que chaque classe enfant peut utiliser ces méthodes dans les programmes.
Surcharger les méthodes parente
Jusqu'à présent, nous avons examiné la classe enfant Truite qui utilisait le mot-clé pass pour hériter de tous les comportements de la classe parente Poisson, et une autre classe enfant Poisson Clown qui a hérité de tous les comportements de la classe parente et a également créé sa propre méthode unique spécifique à la classe enfant. Parfois, cependant, nous voudrons utiliser certains des comportements de la classe parente mais pas tous. Lorsque nous modifions les méthodes de la classe parente, nous les remplaçons.
Lors de la construction de classes parent et enfant, il est important de garder à l'esprit la conception du programme afin que le remplacement ne produise pas de code inutile ou redondant.
Nous allons créer une classe enfant Shark de la classe parente Fish. Étant donné que nous avons créé la classe Fish avec l'idée que nous allions principalement créer des poissons osseux, nous devrons apporter des ajustements pour la classe Shark qui est en réalité un poisson cartilagineux. En termes de conception de programme, si nous avions plus d'un poisson non osseux, nous voudrions probablement créer des classes distinctes pour chacun de ces deux types de poissons.
Les requins, contrairement aux poissons osseux, ont des squelettes faits de cartilage au lieu d'os. Ils ont également des paupières et ne peuvent pas nager à reculons. Les requins peuvent cependant se manœuvrer en arrière en coulant.
À la lumière de cela, nous allons redéfinir la méthode constructeur __init__() et la méthode swim_backwards(). Nous n'avons pas besoin de modifier la méthode swim() puisque les requins sont des poissons qui peuvent nager. Révisons cette classe enfant :
...
class Shark(Fish):
def __init__(self, first_name, last_name="Shark",
skeleton="cartilage", eyelids=True):
self.first_name = first_name
self.last_name = last_name
self.skeleton = skeleton
self.eyelids = eyelids
def swim_backwards(self):
print("The shark cannot swim backwards, but can sink backwards.")
Nous avons remplacé les paramètres initialisés dans la méthode __init__(), de sorte que la variable last_name est maintenant égale à la chaîne "Shark", la variable skeleton est maintenant égale à la chaîne "cartilage", et la variable eyelids est maintenant égale à la valeur booléenne True. Chaque instance de la classe peut également remplacer ces paramètres.
La méthode swim_backwards() imprime maintenant une chaîne différente de celle de la classe parente Fish car les requins ne peuvent pas nager à reculons de la manière dont les poissons osseux peuvent le faire.
Nous pouvons maintenant créer une instance de la classe enfant Shark, qui utilisera toujours la méthode swim() de la classe parente Fish :
...
sammy = Shark("Sammy")
print(sammy.first_name + " " + sammy.last_name)
sammy.swim()
sammy.swim_backwards()
print(sammy.eyelids)
print(sammy.skeleton)
Lorsque nous exécutons ce code, nous recevons la sortie suivante :
Sammy Shark
The fish is swimming.
The shark cannot swim backwards, but can sink backwards.
True
cartilage
La classe enfant Shark a réussi à remplacer les méthodes __init__() et swim_backwards() de la classe parent Fish, tout en héritant également de la méthode swim() de la classe parent.
Lorsque le nombre de classes enfant sera limité et que certaines d'entre elles seront plus uniques que les autres, remplacer les méthodes de la classe parente peut s'avérer utile.
La fonction super()
Avec la fonction super(), vous pouvez accéder aux méthodes héritées qui ont été écrasées dans un objet de classe.
Lorsque nous utilisons la fonction super(), nous appelons une méthode parente dans une méthode enfant pour en faire usage. Par exemple, nous pouvons vouloir remplacer un aspect de la méthode parente par une certaine fonctionnalité, mais ensuite appeler le reste de la méthode parente d'origine pour terminer la méthode.
Dans un programme qui note les étudiants, nous pourrions vouloir avoir une classe enfant pour Weighted_grade qui hérite de la classe parente Grade. Dans la classe enfant Weighted_grade, nous pourrions vouloir remplacer une méthode calculate_grade() de la classe parente afin d'inclure la fonctionnalité de calcul d'une note pondérée, tout en conservant le reste des fonctionnalités de la classe d'origine. En invoquant la fonction super(), nous pourrions y parvenir.
La fonction super() est le plus souvent utilisée dans la méthode __init__() car c'est là que vous aurez le plus besoin d'ajouter une certaine unicité à la classe enfant et ensuite de compléter l'initialisation depuis la classe parente.
Pour voir comment cela fonctionne, modifions notre classe enfant Trout. Comme les truites sont généralement des poissons d'eau douce, ajoutons une variable water à la méthode __init__() et définissons-la égale à la chaîne "eau douce", tout en maintenant les autres variables et paramètres de la classe parente.
...
class Trout(Fish):
def __init__(self, water = "freshwater"):
self.water = water
super().__init__(self)
...
Nous avons substitué la méthode __init__() dans la classe enfant Trout, en fournissant une implémentation différente de la __init__() qui est déjà définie par sa classe parente Fish. Dans la méthode __init__() de notre classe Trout, nous avons explicitement invoqué la méthode __init__() de la classe Fish.
Parce que nous avons remplacé la méthode, nous n'avons plus besoin de passer first_name en tant que paramètre à Trout, et si nous passions un paramètre, nous réinitialiserions freshwater à la place. Nous allons donc initialiser first_name en appelant la variable dans notre instance d'objet.
Maintenant, nous pouvons invoquer les variables initialisées de la classe parente et également utiliser la variable unique de l'enfant. Utilisons cela dans une instance de Trout:
...
terry = Trout()
Initialize first name
terry.first_name = "Terry"
Use parent __init__() through super()
print(terry.first_name + " " + terry.last_name)
print(terry.eyelids)
Use child __init__() override
print(terry.water)
Use parent swim() method
terry.swim()
Terry Fish
False
freshwater
The fish is swimming.
La sortie montre que l'objet terry de la classe enfant Trout est capable d'utiliser à la fois la variable spécifique à l'enfant __init__() water tout en pouvant également appeler les variables de __init__() du parent Fish first_name, last_name et eyelids.
La fonction intégrée de Python super() nous permet d'utiliser les méthodes de la classe parente même lorsque nous remplaçons certains aspects de ces méthodes dans nos classes enfants.
Héritage multiple
L'héritage multiple est lorsque qu'une classe peut hériter des attributs et des méthodes de plus d'une classe parente. Cela peut permettre aux programmes de réduire la redondance, mais cela peut également introduire un certain niveau de complexité ainsi qu'une ambiguïté, donc cela doit être fait en tenant compte de la conception globale du programme.
Pour montrer comment fonctionne l'héritage multiple, créons une classe enfant Coral_reef qui hérite d'une classe Coral et d'une classe Sea_anemone. Nous pouvons créer une méthode dans chacune et ensuite utiliser le mot-clé pass dans la classe enfant Coral_reef :
class Coral:
def community(self):
print("Coral lives in a community.")
class Anemone:
def protect_clownfish(self):
print("The anemone is protecting the clownfish.")
class CoralReef(Coral, Anemone):
pass
La classe Coral a une méthode appelée community() qui imprime une ligne, et la classe Anemone a une méthode appelée protect_clownfish() qui imprime une autre ligne. Ensuite, nous appelons les deux classes dans le tuple d'héritage. Cela signifie que CoralReef hérite de deux classes parentes.
Instancions maintenant un objet CoralReef :
...
great_barrier = CoralReef()
great_barrier.community()
great_barrier.protect_clownfish()
L'objet great_barrier est défini comme un objet CoralReef, et peut utiliser les méthodes dans les deux classes parentes. Lorsque nous exécutons le programme, nous verrons la sortie suivante :
Coral lives in a community.
The anemone is protecting the clownfish.
La sortie montre que les méthodes des deux classes parentes ont été efficacement utilisées dans la classe enfant.
L'héritage multiple nous permet d'utiliser le code de plusieurs classes parentes dans une classe enfant. Si la même méthode est définie dans plusieurs classes parentes, la classe enfant utilisera la méthode du premier parent déclaré dans sa liste de tuples.
Bien qu'il puisse être utilisé efficacement, l'héritage multiple doit être effectué avec précaution afin que nos programmes ne deviennent pas ambigus et difficiles à comprendre pour d'autres programmeurs.
Conclusion
Ce tutoriel a traité de la construction de classes parent et de classes enfant, de la surcharge des méthodes et des attributs parent dans les classes enfant, de l'utilisation de la fonction super(), et de la possibilité pour les classes enfant d'hériter de plusieurs classes parent.
L'héritage en programmation orientée objet peut permettre de respecter le principe DRY (ne vous répétez pas) du développement logiciel, permettant d'accomplir plus avec moins de code et de répétition. L'héritage pousse également les programmeurs à réfléchir à la manière dont ils conçoivent les programmes qu'ils créent pour garantir que le code est efficace et clair.