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 limiter la redondance dans les projets de développement. L'un des moyens permettant à la programmation orientée objet d'obtenir du code recyclable est l'héritage, lorsqu'une sous-classe peut exploiter le code d'une autre classe de base.

Ce didacticiel passera en revue certains des aspects majeurs de l'héritage en Python, y compris le fonctionnement des classes parentes et des classes enfants, comment remplacer les méthodes et les attributs, comment utiliser la fonctionsuper() et comment utiliser l'héritage multiple .

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

Inheritance est quand 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 son parent. 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 avec leurs parents.

Les classes appeléeschild classes ousubclasses héritent des méthodes et des variables deparent classes oubase classes.

On peut penser à une classe parente appeléeParent qui aclass variables pourlast_name,height eteye_color dont la classe enfantChild héritera à partir desParent.

Comme la sous-classeChild hérite de la classe de baseParent, la classeChild peut réutiliser le code deParent, permettant au programmeur d'utiliser moins de lignes de code et de diminuer la redondance .

Classes de parents

Les classes parent ou de base créent un modèle sur lequel des enfants ou des sous-classes peuvent être basés. 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 parente. Il s’agit donc de classes entièrement fonctionnelles et non de modèles.

Supposons que nous ayons une classe parent généraleBank_account qui a les classes enfantsPersonal_account etBusiness_account. De nombreuses méthodes entre les comptes personnels et professionnels seront similaires, telles que les méthodes de retrait et de dépôt d'argent, de sorte qu'elles peuvent appartenir à la classe mère desBank_account. La sous-classeBusiness_account aurait des méthodes qui lui sont spécifiques, y compris peut-être un moyen de collecter des documents et des formulaires commerciaux, ainsi qu'une variableemployee_identification_number.

De même, une classeAnimal peut avoir des méthodeseating() etsleeping(), et une sous-classeSnake peut inclure ses propres méthodeshissing() etslithering() spécifiques.

Créons une classe parenteFish que nous utiliserons plus tard pour construire des types de poissons comme ses sous-classes. Chacun de ces poissons aura des prénoms et des noms en plus des caractéristiques.

Nous allons créer un nouveau fichier appeléfish.py et commencer par les__init__() constructor method, que nous remplirons avec les variables de classefirst_name etlast_name pour chaque objetFish ou sous-classe.

fish.py

class Fish:
    def __init__(self, first_name, last_name="Fish"):
        self.first_name = first_name
        self.last_name = last_name

Nous avons initialisé notre variablelast_name avec la chaîne"Fish" car nous savons que la plupart des poissons auront ce nom de famille.

Ajoutons aussi d’autres méthodes:

fish.py

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éthodesswim() etswim_backwards() à la classeFish, 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 desbony fish (car ils ont un squelette fait d'os) plutôt que comme descartilaginous fish (car ils ont un squelette fait de cartilage) , nous pouvons ajouter quelques attributs supplémentaires à la méthode__init__():

fish.py

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.")

Construire une classe parent suit la même méthodologie que construire n'importe quelle autre classe, sauf que nous réfléchissons aux méthodes que les classes enfants pourront utiliser une fois créées.

Cours enfants

Les enfants ou les 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 enfantGoldfish qui sous-classe la classeFish pourra utiliser la méthodeswim() déclarée dansFish sans avoir besoin de la déclarer.

Nous pouvons considérer chaque classe enfant comme une classe de la classe parent. Autrement dit, si nous avons une classe enfant appeléeRhombus et une classe parent appeléeParallelogram, nous pouvons dire que aRhombusis aParallelogram, tout comme un Goldfishis aFish.

La première ligne d'une classe enfant a un aspect légèrement différent de celui des classes non-enfants, car vous devez transmettre la classe parent à la classe enfant en tant que paramètre:

class Trout(Fish):

La classeTrout est un enfant de la classeFish. Nous le savons grâce à l'inclusion du motFish entre parenthèses.

Avec les classes enfants, nous pouvons choisir d'ajouter plus de méthodes, remplacer les méthodes parentes existantes ou simplement accepter les méthodes parentes par défaut avec le mot-clépass, ce que nous ferons dans ce cas:

fish.py

...
class Trout(Fish):
    pass

Nous pouvons maintenant créer un objetTrout sans avoir à définir de méthode supplémentaire.

fish.py

...
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 objetTroutterry qui utilise chacune des méthodes de la classeFish même si nous n'avons pas défini ces méthodes dans la classe enfantTrout. Nous n'avons eu besoin que de passer la valeur de"Terry" à la variablefirst_name car toutes les autres variables ont été initialisées.

Lorsque nous exécutons le programme, nous recevons le résultat suivant:

OutputTerry 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 classeClownfish, et sa méthode spéciale lui permettra de vivre avec l'anémone de mer:

fish.py

...
class Clownfish(Fish):

    def live_with_anemone(self):
        print("The clownfish is coexisting with sea anemone.")

Ensuite, créons un objetClownfish pour voir comment cela fonctionne:

fish.py

...
casey = Clownfish("Casey")
print(casey.first_name + " " + casey.last_name)
casey.swim()
casey.live_with_anemone()

Lorsque nous exécutons le programme, nous recevons le résultat suivant:

OutputCasey Fish
The fish is swimming.
The clownfish is coexisting with sea anemone.

La sortie montre que l'objetClownfishcasey est capable d'utiliser les méthodesFish__init__() etswim() ainsi que sa méthode de classe enfant delive_with_anemone().

Si nous essayons d'utiliser la méthodelive_with_anemone() dans un objetTrout, nous recevrons une erreur:

Outputterry.live_with_anemone()
AttributeError: 'Trout' object has no attribute 'live_with_anemone'

En effet, la méthodelive_with_anemone() appartient uniquement à la classe enfant deClownfish, et non à la classe parent deFish.

Les classes enfant héritent des méthodes de la classe parente à laquelle elles appartiennent. Ainsi, chaque classe enfant peut utiliser ces méthodes dans des programmes.

Remplacement des méthodes parent

Jusqu'à présent, nous avons examiné la classe enfantTrout qui utilisait le mot-clépass pour hériter de tous les comportements de la classe parentFish, et une autre classe enfantClownfish qui a hérité de tous les comportements de la classe parent et a également créé sa propre méthode unique qui est spécifique à la classe enfant. Parfois, cependant, nous voudrons utiliser certains comportements de la classe parente, mais pas tous. Lorsque nous changeons les méthodes de classe parent, nous lesoverride.

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 enfantShark de la classe parentFish. Parce que nous avons créé la classeFish avec l'idée que nous créerions principalement des poissons osseux, nous devrons faire des ajustements pour la classeShark qui est plutôt un poisson cartilagineux. En ce qui concerne la conception du programme, si nous avions plus d'un poisson non osseux, nous voudrions probablement créer des classes séparées pour chacun de ces deux types de poisson.

Les requins, contrairement aux poissons osseux, ont un squelette en cartilage au lieu d’os. Ils ont aussi les paupières et sont incapables de nager à l'envers. Les requins peuvent toutefois se remettre en marche en coulant.

À la lumière de cela, nous allons remplacer la méthode du constructeur__init__() et la méthodeswim_backwards(). Nous n'avons pas besoin de modifier la méthodeswim() car les requins sont des poissons qui savent nager. Jetons un coup d’œil à cette classe d’enfants:

fish.py

...
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 variablelast_name est maintenant définie égale à la chaîne"Shark", la variableskeleton est maintenant définie égale à la chaîne"cartilage", et la variableeyelids est maintenant définie sur la valeur booléenneTrue. Chaque instance de la classe peut également remplacer ces paramètres.

La méthodeswim_backwards() imprime désormais une chaîne différente de celle de la classe parenteFish car les requins ne sont pas capables de nager en arrière comme le peuvent les poissons osseux.

Nous pouvons maintenant créer une instance de la classe enfantShark, qui utilisera toujours la méthodeswim() de la classe parentFish:

fish.py

...
sammy = Shark("Sammy")
print(sammy.first_name + " " + sammy.last_name)
sammy.swim()
sammy.swim_backwards()
print(sammy.eyelids)
print(sammy.skeleton)

Lorsque nous exécuterons ce code, nous recevrons le résultat suivant:

OutputSammy Shark
The fish is swimming.
The shark cannot swim backwards, but can sink backwards.
True
cartilage

La classe enfantShark a réussi à remplacer les méthodes__init__() etswim_backwards() de la classe parentFish, tout en héritant également de la méthodeswim() de la classe parent.

Lorsqu'il y aura un nombre limité de classes enfant qui sont plus uniques que d'autres, le remplacement des méthodes de classe parent peut s'avérer utile.

La fonctionsuper()

Avec la fonctionsuper(), vous pouvez accéder aux méthodes héritées qui ont été écrasées dans un objet de classe.

Lorsque nous utilisons la fonctionsuper(), nous appelons une méthode parent dans une méthode enfant pour l'utiliser. Par exemple, nous pouvons vouloir remplacer un aspect de la méthode parent par certaines fonctionnalités, puis appeler le reste de la méthode parent d'origine pour terminer la méthode.

Dans un programme qui classe les élèves, nous pouvons souhaiter avoir une classe enfant pourWeighted_grade qui hérite de la classe parent deGrade. Dans la classe enfantWeighted_grade, nous pouvons vouloir remplacer une méthodecalculate_grade() de la classe parent afin d'inclure des fonctionnalités permettant de calculer une note pondérée, tout en conservant le reste des fonctionnalités de la classe d'origine. En invoquant la fonctionsuper(), nous pourrions y parvenir.

La fonctionsuper() est la plus couramment utilisée dans la méthode__init__() car c'est là que vous devrez probablement ajouter un caractère unique à la classe enfant, puis terminer l'initialisation à partir du parent.

Pour voir comment cela fonctionne, modifions notre classe enfantTrout. Les truites étant généralement des poissons d’eau douce, ajoutons une variablewater à la méthode__init__() et définissons-la égale à la chaîne"freshwater", mais conservons ensuite le reste des variables et paramètres de la classe parente:

fish.py

...
class Trout(Fish):
    def __init__(self, water = "freshwater"):
        self.water = water
        super().__init__(self)
...

Nous avons remplacé la méthode__init__() dans la classe enfantTrout, fournissant une implémentation différente des__init__() qui est déjà définie par sa classe parentFish. Dans la méthode__init__() de notre classeTrout, nous avons explicitement invoqué la méthode__init__() de la classeFish.

Parce que nous avons remplacé la méthode, nous n'avons plus besoin de passerfirst_name en paramètre àTrout, et si nous passions un paramètre, nous réinitialiserionsfreshwater à la place. Nous allons donc initialiser lesfirst_name en appelant la variable dans notre instance d'objet.

Nous pouvons maintenant appeler les variables initialisées de la classe parente et également utiliser la variable enfant unique. Utilisons ceci dans une instance deTrout:

fish.py

...
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()
OutputTerry Fish
False
freshwater
The fish is swimming.

La sortie montre que l'objetterry de la classe enfantTrout est capable d'utiliser à la fois la variable__init__() spécifique à l'enfantwater tout en étant capable d'appeler leFish parents__init__() variables defirst_name,last_name eteyelids.

La fonction Python intégréesuper() nous permet d'utiliser des méthodes de classe parente même lors de la substitution de certains aspects de ces méthodes dans nos classes enfants.

Héritage multiple

Multiple inheritance correspond au moment où une classe peut hériter des attributs et des méthodes de plusieurs classes parentes. Cela peut permettre aux programmes de réduire la redondance, mais cela peut également introduire une certaine complexité et de l'ambiguïté. Il convient donc de le faire en pensant à la conception globale du programme.

Pour montrer le fonctionnement de l’héritage multiple, créons une classe enfantCoral_reef qui hérite d’une classeCoral et d’une classeSea_anemone. Nous pouvons créer une méthode dans chacun d'eux puis utiliser le mot-clépass dans la classe enfantCoral_reef:

coral_reef.py

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 classeCoral a une méthode appeléecommunity() qui imprime une ligne, et la classeAnemone a une méthode appeléeprotect_clownfish() qui imprime une autre ligne. Ensuite, nous appelons les deux classes dans l'héritagetuple. Cela signifie queCoral hérite de deux classes parentes.

Instancions maintenant un objetCoral:

coral_reef.py

...
great_barrier = CoralReef()
great_barrier.community()
great_barrier.protect_clownfish()

L'objetgreat_barrier est défini comme un objetCoralReef et peut utiliser les méthodes des deux classes parentes. Lorsque nous exécuterons le programme, nous verrons le résultat suivant:

OutputCoral lives in a community.
The anemone is protecting the clownfish.

La sortie montre que les méthodes des deux classes parent ont été utilisées efficacement dans la classe enfant.

L'héritage multiple nous permet d'utiliser le code de plusieurs classes parentes d'une classe enfant. Si la même méthode est définie dans plusieurs méthodes parent, 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 les autres programmeurs.

Conclusion

Ce didacticiel a permis de construire des classes parentes et des classes enfants, de remplacer les méthodes et attributs parents dans les classes enfants, d'utiliser la fonctionsuper() et de permettre aux classes enfants d'hériter de plusieurs classes parentes.

L’héritage dans le codage orienté objet peut permettre de respecter le principe de développement logiciel DRY (ne vous répétez pas), ce qui permet d’en faire plus avec moins de code et de répétition. Héritage oblige également les programmeurs à réfléchir à la manière dont ils conçoivent les programmes qu'ils créent pour garantir l'efficacité et la clarté du code.