Tutoriel Python : notion d’héritage

Introduction :

Bonjour et Bienvenue dans un nouveau tutoriel Python, aujourd’hui nous allons découvrir une nouvelle notion , il s’agit de l’héritage , c’est l’un des trois piliers de la programmation orientée objet . Dans ce tutoriel, nous allons voir la définition de l’héritage, son utilité et d’autres sections intéressantes.  

Ce tutoriel sera riche en exemple et exercices afin de concrétiser cette notion et de vous permettre de mieux comprendre l’héritage.

Vous devez absolument avoir les notions de bases en programmation orientée objet comme les classes par exemple. Par ailleurs, il suffit de bien suivre les sections du cours et refaire les exemples. Nous estimons que vous êtes impatients de commencer ! Allons-y .

Table des matières

Introduction

Définition de l’héritage

Différence entre type et isinstance

Héritage, notion de overriding

Héritage, avantages et inconvénients

Exemple : l’école

Python et l’héritage multiple

Conclusion

Définition de l’héritage :

Aucun langage de programmation ne serait examiné ou utilisé, s’il ne permettait pas l’héritage. Cette notion a été inventé en 1969  pour Simula, Python offre non seulement l’héritage simple mais aussi l’héritage multiple. Généralement, l’héritage est le processus de dérivation de nouvelles classes à partir de classes existantes afin d’obtenir une hiérarchie de classes. Dans la plupart des langages orientés objet basés sur les classes, un objet créé par héritage (un "objet enfant") reçoit tout, - bien qu’il y ait des exceptions dans certains langages de programmation, - des propriétés et des comportements de l’objet parent.

L’héritage offre aux programmeurs la possibilité de créer des classes à partir de classes existantes. Ainsi, la nouvelle classe héritera des attributs et méthodes de la classe mère. Les méthodes ou de façon générale le processus hérité par une sous-classe est considéré comme réutilisé dans la sous-classe. Les relations d’objets ou de classes par héritage donnent lieu à un graphique dirigé.

Voici un schéma qui vous permet de mieux comprendre la notion d’héritage :

Hiérarchiquement parlant, la classe véhicule est ce qu’on appelle classe mère ou superclasse. La  classe qui hérite d’une classe mère est nommée sous-classe, classe enfant ou  classe héritière. La classe mère ou ce qu’on peut aussi appeler classe ancêtre est la plus générale. Le principe de classes est similaire à la catégorisation dans la vie réelle. Par exemple, dans une école, la classe principale peut être Humain (Nom, prénom, âge …) Ensuite on peut avoir des sous-classes comme professeur ou étudiant. Ces sous-classes hériteront des méthodes et attributs de la classe mère.

Dans l’exemple en dessus, la classe mère est véhicule car bien évidemment voiture, vélo, camions sont considérés autant que véhicules et de même pour leur sous-classes. On pourrait alors créer une classe Véhicule en Python et créer des méthodes comme accélérer  et freiner. Nous verrons tout cela en détail dans les sections suivantes.

Syntaxe générale :

Class nomdelaclassederivée (nomclassemère) :
pass

À la place de l’instruction pass , on implémentera d’autres méthodes et il y’aura d’autres attributs selon le besoin de la classe .

Nous allons faire un exemple de l’héritage simple.

Exemple :

Dans cet exemple, on a deux classes : Humain et homme. La classe Humain étant la classe mère et homme la classe héritière.

Syntaxe :

class Humain:
def __init__(self, nom):
self.name = nom
def say_hi(self):
print("Bonjour , je suis " + self.nom)
class Homme(Humain):
pass
x = Humain ("Marvin")
y = Homme ("James")
print (x, type(x))
print (y, type(y))

Résultat de l’Exécution :

Vous pouvez constater que la classe Homme ne contient ni attributs ni méthodes. Tant que Homme est la sous-classe de Homme, elle hérite des deux méthodes _init_() et say_hi() . Hériter de ces méthodes est l’équivalent de les définir dans la sous-classe Homme. Quand on crée une instance de la classe Homme la fonction _init_() crée automatiquement un attribut nom .

L’exécution retourne le type des deux objets , le premier appartient à la classe humain et le deuxième à la sous-classe Homme .

Différence entre type et isinstance :

Vous devez faire très attention à la différence entre type et isinstance en Python . En effet , isinstance ne retourne true que si on compare un objet avec la classe il appartient ou bien avec la superclasse. Tandis que l’opérateur d’égalité ne retourne true que si l’objet est comparé uniquement avec sa propre classe.

Voici l’application de ses deux fonctions sur les objets de l’exemple précèdent :

Syntaxe :

x = Humain (" Marvin ")
y = Homme (" James ")
print (isinstance (x, Humain), isinstance (y, Humain))
print (isinstance (x, Homme))
print (isinstance (y , Homme ))
print (type(y) == Humain, type(y) == Homme)

Résultat de l’Exécution :

Ceci reste valable même pour les classes dans une lignée héréditaire. Voici un exemple :

Syntaxe :

class une:
pass
class deux(une):
pass
class trois(deux):
pass
x = trois()
print (isinstance(x, une))

Résultat de l’exécution :

Héritage, notion de overriding :

Revenons à l’exemple précèdent des classes Humain et Homme. Imaginons que nous voulons qu’une instance de la classe Homme dise « Salut ,comment ça va ? je suis ..»  à la place de « Bonjour je suis .. » . Est-ce que nous devons définir une nouvelle méthode ? NON . C’est ici ou vient l’importance de la notion Overriding .

C’est le processus de redéfinir une méthode de la classe mère dans la classe enfant .

Dans notre exemple , on va redéfinir la méthode say_hi() dans la classe Homme :

Syntaxe :

class Humain:
def __init__(self, nom):
self.nom = nom
def say_hi(self):
print("Bonjour, Je suis " + self.nom)
class Homme (Humain):
def say_hi(self):
print("Salut , comment ça va ? Je suis " + self.nom)
y = Homme("James")
y.say_hi()

Résultat de l’Execution:

Voilà, comme vous pouvez le voir, un nouveau message est affiché.

Si une méthode est redéfinie dans une sous-classe, on peut toujours accéder à la méthode original mais en utilisant le nom de la classe. Par exemple, si on regrette notre choix, et qu’on veut afficher la première version du say_hi() (Celui définit dans la classe mère ) voici comment faire :

Syntaxe :

y = Homme ("James")
y.say_hi()
print ( " ... et maintenant on revient à l’ancienne méthode du say_hi ! " )
Humain.say_hi(y)

Résultat de l’Execution:

Jusqu’à maintenant, nous savons qu’une classe enfant hérite tous les attributs et toutes les méthodes de la classe mère. Par ailleurs , la classe enfant a souvent besoins de quelques fonctions spécifiques à elle-même… Par exemple une instance d’une classe peut avoir besoin de manger , conduire , dormir…

Ce sont des fonctions qu’on ajoute facilement dans le corps de la classe :

class Humain:
def __init__(self, nom):
self.nom = nom
def say_hi(self):
print(" Bonjour, Je suis " + self.nom)
class Homme (Humain):
def say_hi(self):
print(" Salut , comment ça va ? Je suis " + self.nom)
def manger (self):
print( self.nom + " est en train de manger " )
def conduire (self):
print( self.nom + " est entrain de conduire " )
def dormir (self):
print( self.nom + " est entrain de dormir " )
y = Homme("James")
y.say_hi()
y.manger()
y.conduire ()
y.dormir()

Résultat de l’Execution:

Des fois, quand on veut redéfinir une méthode, on peut avoir besoin de la méthode de la classe mère. Par exemple, on va redéfinir la méthode say_hi() de tel manière qu’elle affiche le message de la classe mère plus « Comment ça va » :

Syntaxe :

class Humain:
def __init__(self, nom):
self.nom = nom
def say_hi(self):
print(" Bonjour, Je suis " + self.nom)
class Homme (Humain):
def say_hi(self):
Humain.say_hi(self)
print(" Comment ca va ? ")
def manger (self):
print( self.nom + " est en train de manger " )
def conduire (self):
print( self.nom + " est entrain de conduire " )
def dormir (self):
print( self.nom + " est entrain de dormir " )
y = Homme("James")
y.say_hi()

Résultat de l’Execution:

Héritage, avantages et inconvénients :

Comme chaque notion de la programmation, la notion d’héritage a des avantages comme des inconvénients :

Avantages :

  • La réutilisabilité, une classe enfant peut accéder à toutes les fonctionnalités de la classe mère.
  • Pas besoin de re-tester le code vu que celui de base sera déjà testé et débogué.
  • Moins de maintenance et de coûts de développement.
  • Réduction du code et soutien de l’extensibilité du code
  • Facilite la création des librairies

Inconvénients :

  • Fonctionnement  plus long que celui de la fonction normale
  • L’héritage est sensible, une mauvaise utilisation peut causer des bugs
  • Gaspillage de mémoire vu que la classe enfant hérite de toutes les fonctionnalités de la classe mère

Nous pensons que vous allez fermer l’œil sur les inconvénients et terminer ce tutoriel.

Nous allons passer à des exemples pour vous mettre dans le bain !

Exemple : l’école

  

Dans cet exemple, on veut avoir une classe mère Personne et trois classes enfants : Etudiant, Responsable  et professeur.

Classe mère : Personne

Cette première classe définit une personne en général :

Attributs de la classe :

  • Nom
  • Prénom
  • Age
  • Ville
  • Genre

Méthodes de la classe Personne :

  • GetNom : retourne le nom
  • GetPrenom : retourne le prénom
  • GetAge : retourne l’Age
  • GetVille : retourne la ville
  • GetGenre : retourne le genre de la personne (femme, homme)

  Syntaxe :

class Personne:
def __init__(self, nom , prenom ,age ,ville , genre):
self.nom = nom
self.prenom = prenom
self.age = age
self.ville = ville
self.genre = genre
def getNom (self):
print("le nom de la personne est " + self.nom)
def getPrenom (self):
print("le prénom de la personne est " + self.prenom)
def getAge (self):
print("le prénom de la personne est " + self.age)
def getVille (self):
print("la ville de la personne est " + self.ville)
def getGenre(self):
print("le Genre de la personne est " + self.genre)

Les classes suivantes vont hériter de la classe Personne donc pas besoin de redéfinir ces attributs pour chaque classe enfant. Voilà à quoi sert l’héritage chers lecteurs.

Chaque élève, Responsable ou Professeur et maintenant considéré comme personne ayant un nom, un prénom …

Maintenant nous allons définir les autres classes et les méthodes spécifiques à chacune d’entre elles.

Remarque : Dans les classes suivantes les attributs et méthodes en bleu sont celle hérités de la classe mère.

 Classe enfant 1: Etudiant

 Attributs :

  • Nom
  • Prénom
  • Age
  • Ville
  • Genre
  • Numero_etudiant
  • Niveau
  • Filière
  • Points_assiduité

Méthodes :

  • GetNom : retourne le nom
  • GetPrenom : retourne le prénom
  • GetAge : retourne l’Age
  • GetVille : retourne la ville
  • GetGenre : retourne le genre de la personne (femme, homme)
  • GetNumEtud : retourne le numéro_étudiant de l’élève
  • GetNiveau : retourne le niveau de l’élève
  • GetFilière : retourne la filière ou est inscrit l’élève
  • GetPointsAssidi : retourne le nombre de points d’assiduité de l’élève

Syntaxe :

class Etudiant (Personne):
def __init__(self, nom, prenom , age ,ville ,genre ,Numero_etudiant , niveau , filiere , points_assiduité ):
super().__init__(nom,prenom,age,ville,genre)
self.niveau = niveau
self.filiere = filiere
self.points_assiduité = points_assiduité
def getNiveau ():
print ("le niveau de l'élève est : " + self.niveau)
def getNumeroEtud ():
print ("le numéro d'étudiant de l'élève est: " + self.Numero_etudiant)
def getFiliere ():
print ("la filière de l'élève est : " + self.filiere )
def getPointAssiduité ():
print ("le nombre de points de l'élève est : " + self.points_assiduité )

Classe enfant 2 : Responsable

Attributs :

  • Nom
  • Prénom
  • Age
  • Ville
  • Genre
  • Fonction
  • Numero_securite
  • Département

Méthodes :

  • GetNom : retourne le nom
  • GetPrenom : retourne le prénom
  • GetAge : retourne l’Age
  • GetVille : retourne la ville
  • GetGenre : retourne le genre de la personne (femme, homme)
  • GetFonction :retourne la fonction du responsable
  • GetNumSecu :retourne le numéro de sécurité du responsable
  • GetDepartement :retourne le département ou travaille le responsable

Syntaxe :

class Responsable (Personne):
def __init__(self, nom, prenom, age , ville , genre , fonction , numero_securite , departement ):
super().__init__(nom ,prenom ,age ,ville ,genre )
self.fonction = fonction
self.numero_securite = numero_securite
self.departement = departement
def getFonction () :
print ("la fonction du responsable est : " + self.fonction )
def getNumerosecurite () :
print ("le numéro de sécurité du responsable est: " + self.numero_securite)
def getDepartement () :
print ("le département du Responsable est : " + self.departement )

Classe enfant 3 : Professeur

Attributs :

  • Nom
  • Prénom
  • Age
  • Ville
  • Genre
  • Matière
  • Niveau
  • Nb_heures

Méthodes :

  • GetNom : retourne le nom
  • GetPrenom : retourne le prénom
  • GetAge : retourne l’Age
  • GetVille : retourne la ville
  • GetGenre : retourne le genre de la personne (femme, homme)
  • GetMatière :retourne la matière qu’enseigne le professeur.
  • GetNiveau:retourne le niveau qu’enseigne le professeur.
  • GetNbheures :retourne le nombre d’heures qu’enseigne le professeur.

Syntaxe :

class Professeur (Personne):
def __init__(self, nom, prenom, age ,ville ,genre , matiere ,niveau ,nb_heures):
super().__init__(nom, prenom ,age ,ville ,genre )
self.matiere = matiere
self.niveau = niveau
self.nb_heures = nb_heures
def getMatiere ():
print (" la matière du professeur est : " +self.matiere )
def getNiveau ():
print (" le niveau qu'enseigne le professeur est: " + self.niveau)
def getNbHeures ():
print (" le nombre d'heures qu'enseigne le professeur est : " + self.nb_heures )

Python et l’héritage multiple :

Jusqu’à présent, vous ne connaissez que l’héritage simple entre une classe mère et une classe enfant . Mais vous vous poserez surement la question suivante : Est-il Possible qu’une classe enfant hérite de plusieurs classes ?  En Python, oui, l’héritage multiple est autorisé .

L’héritage multiple est le processus ou une classe enfant hérite des attributs et méthodes de plusieurs classes mères . C’est une notion très complexe et ambiguë mais qui  est bien supportée par Python .

Voici la syntaxe générale de l’héritage multiple :

class NomSousclasse(Classe1, Classe2, Classe3, ...):
pass

Les classe1 , classe2 et classe3 peuvent elles-mêmes hériter d’autres super classes chose qui nous créera une arborescence complexe :

Exemple :

Voici un exemple qui concrétise l’héritage multiple, la classe 3 hérite des classes une et deux simultanément.

Syntaxe :

class une (object):
def __init__(self):
self.st1 = "Bonjour"
print ("A")
class deux (object):
def __init__(self):
self.st2 = "Lecteurs"
print ("B")
class trois (une , deux):
def __init__(self):
# Appel le constructeur de la classe une et deux
une.__init__(self)
deux.__init__(self)
print ("C")
def afficher (self):
print (self.st1 , self.st2)
c = C()
c.afficherMsg()

Résultat de l’Execution:

Conclusion :

Nous sommes arrivés à la fin de ce tutoriel ! Vous venez d’acquérir  une notion essentielle en programmation qui vous économisera beaucoup de temps et rendra votre code plus claire et agréable à lire. Nous vous conseillons de revoir les exemples et essayer de les implémenter à votre manière ainsi que de créer vos propres programmes.

Voila ! La route est encore longue, vous découvrirez encore plus de notions dans le futur mais ce que nous pouvons vous dire est que vous êtes sur le droit chemin. Bon courage, et à un prochain tutoriel J .

Article publié le 18 Octobre 2020par Mouna HAMIM