Classes Python : créer de nouveaux objets

L'instruction class permet de définir un nouveau type d'objet en python.

Cela peut être particulièrement utile pour structurer un programme et construire des représentations claires de certains objets.

image20

Faisons nos classes !

Une classe d'objets peut définir plusieurs fonctions associées à ces objets (qui seront appelées avec la notation '.') : ce sont les méthodes de la classe.

Elle peut aussi définir des données associées à ces objets : on parle alors d'attributs.

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 un attribut 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\).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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"""
        d = 0
        for k in range(len(self.coeffs)) :
            if self.coeffs[k] != 0 :
                d = k
        return d

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

Utilisation de notre nouvelle classe

Pour définir le polynôme \(P(x)=2x+3x^2+x^3\) et, par exemple, afficher son degré et sa valeur pour \(x=10\), on écrira dans la console :

1
2
3
4
5
6
7
>>> p = Polynome([0, 2, 3, 1])
>>> print(p.deg())
3
>>> print(p.valeur(10))
1320
>>> print(p.coeffs)
[0, 2, 3, 1]

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 l'attribut p.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 :

1
2
>>> print(p)
<__main__.Polynome object at 0x7f642d243e90>

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 suivante dans la définition de la classe Polynome() :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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

Maintenant, la commande print() retourne :

1
2
3
>>> p = Polynome([0, 2, 3, 1])
>>> print(p)
2X + 3X^2 + X^3

Addition de deux 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 :

1
2
3
4
5
6
7
>>> p = Polynome([0, 2, 3, 1])
>>> q = Polynome([0, 0, 0, 0, 0, 0, 1])
>>> s=p+q
Traceback (most recent call last):
  File "", line 1, in 
    s=p+q
TypeError: unsupported operand type(s) for +: 'Polynome' and 'Polynome'

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.

Cette méthode doit être ajoutée dans la définition de la classe.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
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

On obtient alors :

1
2
3
4
5
>>> p = Polynome([0, 2, 3, 1])
>>> q = Polynome([0, 0, 0, 0, 0, 0, 1])
>>> s = p + q
>>> print(s)
2X + 3X^2 + X^3 + X^6

Autres méthodes spéciales

On peut également définir des méthodes __sub__() pour la soustraction, __floordiv__() pour le quotient de la division euclidienne (obtenu par l'opérateur //), __mod__() pour le reste de la division euclidienne (obtenu par l'opérateur %), __divmod__() qui retourne le quotient et le reste.

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

Cette possibilité de surcharge de méthodes existantes pour leur faire effectuer une action adaptée à un objet particulier est une exemple du concept plus général de polymorphisme : une même opération, par exemple ici l'addition, effectuera une action différente suivant le type des objets auxquels elle s'applique.

Le concept d'héritage

Nous allons maintenant définir une nouvelle classe d'objets pour représenter des polynômes du second degré.

Une méthode de calcul des racines éventuelles sera implémentée et un nouvel attribut, \(\Delta\) correspondant au discriminant.

Une fille de classe

Un polynôme du second degré est néanmoins un polynôme comme les autres. Nous allons donc définir la classe Trinome() comme une classe enfant de la classe Polynome() : elle héritera ainsi de toutes les méthodes et attributs de la classe Polynome(). Pour cela, il suffit de faire référence à la classe "mère" lors de la définition de la classe "fille".

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
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)
        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 :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
>>> t1 = Trinome([2, -3, 5])
>>> print(t1)
2 - 3X + 5X^2
>>> t1.delta
-31
>>> print(t1.racines())
None
>>> t1.valeur(2)
16
>>> t2=Trinome([4, -4, 1])
>>> print(t2)
4 - 4X + X^2
>>> t2.delta
0
>>> print(t2.racines())
2.0
>>> t2.valeur(6)
16
>>> t3=Trinome([12, -7, 1])
>>> print(t3)
12 - 7X + X^2
>>> t3.delta
1
>>> print(t3.racines())
(3.0, 4.0)
>>> t3.valeur(2)
2

Exercices

Exercice 1

Créer une classe pour représenter les nombre rationnels. Définir 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 .

Exercice 2

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

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

Attention, tous les attributs doivent être initialisés par la méthode constructeur de la classe !

image1