Programmation Orientée Objet - Partie 2/2

Dans cette seconde partie, nous allons voir l'utilisation des classes dans un cas concrêt : Apprendre à Python à travailler sur les polynomes.

Faisons nos classes !

Un exemple valant mieux qu'un long discours, supposons que je sois en train de développer un programme permettant de travailler sur les polynômes.

Je peux définir un objet représentant un polynôme en général. On peut choisir de représenter les coefficients par une liste de nombres, qui sera donc une propriété de notre classe et définir une méthode permettant de calculer le degré du polynôme.

Avec ces conventions, la liste [1,2,3] représente le polynôme $1+2x+3x^2$.

Exemple de classe

Voici le code définissant la classe Polynome avec une méthode pour calculer le degré et une autre pour calculer une valeur en un réel $x$

In [ ]:
class Polynome :
    """Représentation d'un polynome à coefficients réels"""

    def __init__(self, liste_coeffs = [0]) :
        """Initialisation des coeffs, polynome nul par défaut"""
        self.coeffs = liste_coeffs

    def deg(self) :
        """Degré du polynome"""
        return len(self.coeffs)-1

    def valeur(self, x) :
        """Calcule P(x)"""
        val = self.coeffs[0]
        power = 1
        for k  in range(1, len(self.coeffs)) :
            power = power * x
            val = val + self.coeffs[k]*power
        return val

Explications et remarques

  • Par convention, on mettra une majuscule à la première lettre du nom d'une classe, pour les différencier des autres variables, fonctions qui, elles, débuteront toujours par une lettre minuscule.
  • La première méthode définie ci-dessous porte le nom spécial __init__() : il s'agit de la méthode constructeur : elle est automatiquement exécutée lors de la création d'un nouvel objet de type Polynome (voir plus loin).
  • Dans cette méthode, nous déclarons une propriété à notre classe par l'affectation self.coeffs = . Une propriété est une variable qui est attachée à la classe, d'où le recours à self pour référencer l'objet lui-même. En général, ces propriétés sont initialisées dans la méthode __init__().
  • Chacune des trois méthodes possède comme premier argument le paramètre spécial self : il représente l'objet "lui-même" dont on est en train de définir une méthode. La référence à cet objet est obligatoire : Toute déclaration de méthode doit contenir self comme premier paramètre.
  • On le voit sur cet exemple, une méthode n'est pas grand chose de plus qu'une fonction telle que vous la conaissez ! la manière de la déclarer dans une classe est assez similaire - au paramètre self près.

L'objet Polynome() que nous venons de créer possède

  • une propriété : la liste coeffs
  • deux méthodes en plus de __init()__ (qui elle est systématique) : deg() et valeur()

L'accès à la propriété coeffs de notre classe se fait au travers de la variable self.coeffs

Utilisons notre nouvelle classe

In [ ]:
p = Polynome([0, 2, 3, 1])
print("P est de degré ",p.deg())
print("P(10)=",p.valeur(10))
print("Les coeffs de P sont ",p.coeffs)

Explications et remarques

  • Lors de la création d'un nouveau polynôme, on appelle la classe Polynome() avec comme argument la liste des coefficients. Cela a pour effet d'exécuter la méthode constructeur __init__() de la classe Polynome() qui crée la propriété coeffs correspondant.
  • Pour exécuter une méthode associée à l'objet p, on utilise la notation pointée et on omet l'argument self : celui-ci n'est précisé que lors de la définition d'une méthode, mais pas lors de son exécution.

Un peu de magie : surcharge de fonctions prédéfinies

Affichage d'un polynôme

Pour afficher un polynôme, la commande print() ne donne pas le résultat attendu :

In [ ]:
print(p)

Pour parvenir au résultat attendu, on peut surcharger la fonction print(). Plus précisément, on peut indiquer à Python comment convertir un polynôme en chaîne de caractères, ce que fera ensuite automatiquement la commande print().

Pour cela, on ajoutera la méthode __str__() dans la définition de la classe Polynome(). Celle-ci est un peu longue à écrire mais ce qu'il faut comprendre ici, c'est le principe de l'ajout de cette fonction.

In [ ]:
class Polynome :
    """Représentation d'un polynome à coefficients réels"""

    def __init__(self, liste_coeffs = [0]) :
        """Initialisation des coeffs, polynome nul par défaut"""
        self.coeffs = liste_coeffs

    def deg(self) :
        """Degré du polynome"""
        return len(self.coeffs)-1

    def valeur(self, x) :
        """Calcule P(x)"""
        val = self.coeffs[0]
        power = 1
        for k  in range(1, len(self.coeffs)) :
            power = power * x
            val = val + self.coeffs[k]*power
        return val
    
    def __str__(self) :
        """ Convertit le polynome en chaine pour affichage"""
        chaine=""
        k = 0
        # recherche du premier coefficient non nul
        while self.coeffs[k] == 0 :
            k = k+1
        # écriture du terme de plus petit degré
        if k == 0 :
            if self.coeffs[k] != 0 :
                chaine = str(self.coeffs[k])
        elif k == 1 :
            if self.coeffs[k] == 1 :
                chaine = "X"
            elif self.coeffs[k] != 0 :
                chaine = str(self.coeffs[k]) + "X"
        else :
            if self.coeffs[k] == 1 :
                chaine = "X^" + str(k)
            elif self.coeffs[k] != 0 :
                chaine = str(self.coeffs[k]) + "X^"+str(k)
        # écriture des termes suivants
        for i in range(k+1, len(self.coeffs)) :
            if self.coeffs[i] == 1 :
                if i == 1 :
                    chaine = chaine + " + " + "X"
                else :
                    chaine = chaine + " + " + "X^"+str(i)
            elif self.coeffs[i] > 0 :
                if i == 1 :
                    chaine = chaine + " + " + str(self.coeffs[i]) + "X"
                else :
                    chaine = chaine + " + " + str(self.coeffs[i]) + "X^"+str(i)
            elif self.coeffs[i] < 0 :
                if i == 1 :
                    chaine = chaine + " - " + str(abs(self.coeffs[i])) + "X"
                else :
                    chaine = chaine + " - " + str(abs(self.coeffs[i])) + "X^"+str(i)
        return chaine
In [ ]:
p = Polynome([0, 2, 3, 1])
print(p)

Addition de polynômes

Soit les polynômes $P(x)=2x+3x^2+x^3$ et $Q(x)=x^6$. Pour obtenir le polynôme $P+Q$, on aimerait utiliser simplement l'opérateur '+'. Mais voilà ce qui arrive

In [ ]:
p = Polynome([0, 2, 3, 1])
q = Polynome([0, 0, 0, 0, 0, 0, 1])
s = p + q
print(s)

Là encore, pour résoudre ce problème, on peut surcharger l'addition en définissant la méthode spéciale __add__(), c'est-à-dire apprendre à Python comment on additionne deux polynômes.

Nous complétons donc à nouveau notre classe Polynomes()

In [ ]:
class Polynome :
    """Représentation d'un polynome à coefficients réels"""

    def __init__(self, liste_coeffs = [0]) :
        """Initialisation des coeffs, polynome nul par défaut"""
        self.coeffs = liste_coeffs

    def deg(self) :
        """Degré du polynome"""
        return len(self.coeffs)-1

    def valeur(self, x) :
        """Calcule P(x)"""
        val = self.coeffs[0]
        power = 1
        for k  in range(1, len(self.coeffs)) :
            power = power * x
            val = val + self.coeffs[k]*power
        return val
    
    def __str__(self) :
        """ Convertit le polynome en chaine pour affichage"""
        chaine=""
        k = 0
        # recherche du premier coefficient non nul
        while self.coeffs[k] == 0 :
            k = k+1
        # écriture du terme de plus petit degré
        if k == 0 :
            if self.coeffs[k] != 0 :
                chaine = str(self.coeffs[k])
        elif k == 1 :
            if self.coeffs[k] == 1 :
                chaine = "X"
            elif self.coeffs[k] != 0 :
                chaine = str(self.coeffs[k]) + "X"
        else :
            if self.coeffs[k] == 1 :
                chaine = "X^" + str(k)
            elif self.coeffs[k] != 0 :
                chaine = str(self.coeffs[k]) + "X^"+str(k)
        # écriture des termes suivants
        for i in range(k+1, len(self.coeffs)) :
            if self.coeffs[i] == 1 :
                if i == 1 :
                    chaine = chaine + " + " + "X"
                else :
                    chaine = chaine + " + " + "X^"+str(i)
            elif self.coeffs[i] > 0 :
                if i == 1 :
                    chaine = chaine + " + " + str(self.coeffs[i]) + "X"
                else :
                    chaine = chaine + " + " + str(self.coeffs[i]) + "X^"+str(i)
            elif self.coeffs[i] < 0 :
                if i == 1 :
                    chaine = chaine + " - " + str(abs(self.coeffs[i])) + "X"
                else :
                    chaine = chaine + " - " + str(abs(self.coeffs[i])) + "X^"+str(i)
        return chaine
    
    def __add__(self, poly) :
        """retourne la somme de deux polynomes"""
        coeffs_somme=[]
        if self.deg() <= poly.deg() :
            for i in range(self.deg()+1) :
                coeffs_somme.append(self.coeffs[i] + poly.coeffs[i])
            for i in range(self.deg()+1, poly.deg()+1) :
                coeffs_somme.append(poly.coeffs[i])
        else :
            for i in range(poly.deg()+1) :
                coeffs_somme.append(self.coeffs[i] + poly.coeffs[i])
            for i in range(poly.deg()+1, self.deg()+1) :
                coeffs_somme.append(self.coeffs[i])
        somme = Polynome(coeffs_somme)
        return somme

Essayons à nouveau l'addition de nos polynômes

In [ ]:
p = Polynome([0, 2, 3, 1])
q = Polynome([0, 0, 0, 0, 0, 0, 1])
s = p + q
print(s)

Autres méthodes spéciales

On peut également définir des méthodes __sub__() pour la soustraction, __mul__() pour la multiplication, __truediv__() pour la division, etc...

D'autres méthodes spéciales existent : la liste complète est disponible dans la documentation de Python.

Le concept d'héritage

L'un des grands avantages des objets est l'héritage. Cela permet de personaliser une classe en héritant des propriétés et méthodes d'une autre classe.

Nous allons en voir un exemple en créant une classe Trinome() pour le cas particulier des polynômes du second degré. En effet, un polynome du second degré étant un cas particulier de polynome, nous ne voulons pas réécrire tout le code que nous venons de créer, notamment pour l'affichage et l'addition. Néanmoins, pour le trinome, nous savons calculer les racines et nous souhaitons donc enrichir notre classe Trinome() avec une méthode suplémentaire appelée racines(). Celle-ci utilisera une nouvelle propriété delta créée lors de l'initialisation de notre classe.

Pour éviter de réécrire toutes les fonctions propres aux polynomes, nous allons faire hériter notre classe Trinome() de la classe Polynomes().

Regardez plutôt avec quelle facilité à présent nous allons créer notre classe Trinome() :

In [ ]:
class Trinome(Polynome) :
    """ Représentation des polynomes du second degré"""
    
    def __init__(self, liste_coeffs=[0,0,1]) :
        """ Initialisation d'un trinome, x^2 par défaut """
        Polynome.__init__(self, liste_coeffs) # On appelle le constructeur parent
        self.a = liste_coeffs[2]
        self.b = liste_coeffs[1]
        self.c = liste_coeffs[0]
        self.delta = self.b ** 2 - 4 * self.a * self.c

    def racines(self) :
        """ Calcule les racines éventuelles d'un trinome """
        if self.delta < 0 :
            return None
        elif self.delta == 0 :
            return -self.b / (2 * self.a)
        else :
            return ( (- self.b - sqrt(self.delta)) / (2 * self.a) ,
                     (- self.b + sqrt(self.delta)) / (2 * self.a) )

Explications et remarques

  • La méthode constructeur __init__() de la classe fille doit obligatoirement appeler la méthode constructeur de sa mère. C'est le rôle ici de la ligne 6.
  • On définit ensuite les nouveaux attributs propres aux objets de la classe Trinome().

Utilisation de la nouvelle classe

Testons maintenant notre nouvelle classe Trinome().

Cette classe ayant été explicitement définie comme fille de la classe Polynome(), elle a hérité de toutes les méthodes et de tous les attributs de celle-ci.

On peut donc exécuter le code suivant :

In [ ]:
t1 = Trinome([2, -3, -5])
print(t1)
print("Delta=",t1.delta)
print("Racines",t1.racines())
print(t1.valeur(0.4))

A vous de jouer

Créer une classe pour représenter les nombre rationnels.

Vous définirez les méthodes permettant d’additionner, de soustraire, de multiplier et de diviser deux rationnels, ainsi qu'une méthode permettant un affichage sous la forme a/b.

In [ ]:
# Votre classe ici
In [ ]:
# Tester votre classe
p=Rationnel(2,3)
q=Rationnel(3,4)
print ("p=",p)
print ("q=",q)
print("p+q",p+q)
print("p-q",p-q)
print("p*q",p*q)
print("p/q",p/q)

Exercice facultatif

Pour la gestion d'une bibliothèque, créer une classe Document() définissant une propriété booléen sorti, une propriété titre sous forme de chaîne de caractère, une méthode prete() et une méthode retourne() qui changent la valeur de la propriété sorti.

Créer ensuite une classe fille Livre() qui possédera en plus une propriété auteur et une propriété nombre_de_pages ainsi qu'une classe fille Dvd() avec une propriété duree en minutes.

Attention, toutes les propriétés doivent être initialisées par la méthode constructeur de la classe !

In [ ]:
# Votre classe ici

In [ ]: