1A.2 - Classes, méthodes, attributs, opérateurs et carré magique

Links: notebook, html, PDF, python, slides, GitHub

Les classes proposent une façon différente de structurer un programme informatique. Pas indispensable mais souvent élégant.

from jyquickhelper import add_notebook_menu
add_notebook_menu()

La plupart du temps, les classes ne sont pas indispensables et l’expérience montre que la plupart des élèves choisissent de s’en dispenser lors de la réalisation de leur projet. Pourquoi les aborder ? Pour plusieurs raisons :

  • Un langage de programmation dispose des types standards : entier, réel, chaînes de caractères, tableaux. Hors de cette liste, il n’y a rien à moins de pouvoir le créer, c’est ce que permettent les classes.
  • Elles rendent le programme plus lisible : tous les projets conséquents utilisent les classes. C’est un peu comme si on disposait d’un vocabulaire enrichi pour décrire un programme.

Prenons l’exemple d’un jeu de cartes. Une carte désigne l’objet physique : sa couleur, son numéro (ou figure), son atout… Dans un jeu, chaque carte vaut un certain nombre de points, elle est plus ou moins forte qu’une autre… Comment décrire une carte de tarot ?

option 1 option 2 option 3
une couleur une couleur une couleur
un numéro un numéro (atout = nombre > 100) un numéro (atout = nombre > 100)
un atout   un nombre de points

Définir une classe dans votre programme vous permet de définir précisément ce que le concept signifie selon trois aspects :

  • les attributs : les données que la classe considère comme un tout,
  • les méthodes : des fonctions opérant sur les attributs,
  • les opérateurs : des fonctions spécifiques pour définir ce qu’est une addition, une soustraction…

Aparté sur les classes

On ne voit pas toujours l’intérêt d’utiliser des classes pour un exemple peut-être trop simple que celui-ci qui suit. Imaginons une fonction d’optimisation avec 10 paramètres comme celle-ci scipy.optimize.minimize où les paramètres sont transmis sous forme de dictionnaire. Pourquoi faire ça ? Souvent parce que les paramètres doivent être transmis à de nombreuses autres fonctions et qu’il serait fastidieux de répéter à chaque fois la liste des paramètres. Au lieu d’utiliser un dictionnaire, on peut également créer une classe qui regrouperait des paramètres :

class MesParametres:
    def __init__(self):
        self.pas_gradient = 0.001
        self.constante =  2.0
        self.iteration = 1000
        # ...

Ensuite, il suffit de créer une instance cette classe :

p = MesParametres()

Et maintenant, il suffit d’écrire une fonction utilisant les paramètres :

def fonction_any(f, x, p):
    for i in range(0,p.iteration):
        # ...
        pass

A quoi sert le paramètre self, une raisons est qu’on peut créer plein d’instances de paramètres :

p1 = MesParametres()
p2 = MesParametres()
p3 = MesParametres()

Cela veut dire plusieurs variables p1, p2, p3 mais une seule façon de les définir toutes, une façon de dire que le code ci-dessus est à la fois valide pour p1, p2 et p3. On a besoin d’une sorte de pronom.

Classes, attributs, opérateurs

On veut définir une classe Point équivalent à un point dans le plan.

class Point :
    def __init__ (self, x,y) :
        self.x = x
        self.y = y

De cette façon, on peut définir un point de coordonnées (4,5) :

p = Point (4,5)

Vocabulaire :

  • p est une instance de la classe Point. Il n’existe qu’une classe Point mais autant d’instances qu’on veut. Dans notre cas : instance = variable de type Point.
  • __init__ : est un constructeur. Il définit ce que Python doit faire lorsqu’on crée une instance.
  • self.x, self.y sont des attributs (ou des variables à l’intérieur d’une classe).

La variable self peut être remplacée par n’importe quoi. C’est une convention de langage pour désigner l’instance manipulée à l’intérieur de la classe :

class Point :
    def __init__ (moi, x,y) :
        moi.x = x
        moi.y = y

p1 = Point(4,5)  # moi désigne p1 ici
p2 = Point(6,7)  # moi désigne p2 ici

Si on utilise print :

print (p1)
<__main__.Point object at 0x00000000072AA208>

Pour éviter cela, on peut afficher directement x et y :

print (p1.x, p1.y)
4 5

Ou ajouter l’opérateur __str__ :

class Point :
    def __init__ (moi, x,y) :
        moi.x = x
        moi.y = y
    def __str__(moi):
        return "point: ({0},{1})".format(moi.x, moi.y)

p = Point(4,5)
print (p)
point: (4,5)

On peut également définir l’addition :

class Point :
    def __init__ (moi, x,y) :
        moi.x = x
        moi.y = y
    def __str__(moi):
        return "{0},{1}".format(moi.x, moi.y)
    def __add__(moi, autre_point) :
        return Point(moi.x + autre_point.x, moi.y + autre_point.y)

p = Point(4,5)
print (p + p)
8,10

On peut redéfinir tous les opérateurs numériques mais il en existe beaucoup d’autres comme l’opérateur [] (voir container.

Exercice 1 : carré magique

On souhaite appliqer ce qu’on vient de voir pour définir un carré magique qui contient neuf chiffres rangés dans un tableau à deux dimensions. On ajoutera l’opérateur __str__.

class CarreMagique :
    def __init__(self, ...) :
        # ......
    def __str__(self) :
        # ......
    def __add__(self) :
        # ......

Méthodes

Une méthode est une fonction rattachée à une classe et qui s’applique aux données de la classe et celles envoyées en paramètres :

class Point :
    def __init__ (moi, x,y) :
        moi.x = x
        moi.y = y
    def norm(moi) :
        return (moi.x**2 + moi.y**2) ** 0.5

p = Point(4,5)
print (p.norm())
6.4031242374328485

Avec un paramètre :

class Point :
    def __init__ (moi, x,y) :
        moi.x = x
        moi.y = y
    def norm(moi, lx = 2) :
        return (abs(moi.x)**lx + abs(moi.y)**lx) ** (1.0 / lx)

p = Point(4,5)
print (p.norm(1))
print (p.norm(2))
print (p.norm(3))
print (p.norm(100))
9.0
6.4031242374328485
5.738793548317167
5.000000000010186

On peut bien sûr appeler une méthode de la classe depuis une autre méthode de la même classe :

class Point :
    def __init__ (moi, x,y) :
        moi.x = x
        moi.y = y
    def norm(moi, lx = 2) :
        if lx == 0 : return moi.est_nul()
        else : return (abs(moi.x)**lx + abs(moi.y)**lx) ** (1.0 / lx)
    def est_nul(moi):
        return  moi.x == 0 and moi.y == 0

p0 = Point(0,0)
p  = Point(0,4)
print(p0.est_nul())
print(p.est_nul())
True
False

Exercice 2 : à faire à trois, carré magique (suite)

Ajouter trois méthodes à la classe carré magique :

  • une méthode qui compte la somme des nombres sur chaque ligne, colonne, diagonale
  • une méthode qui dit si tous les chiffres du carrés sont uniques,
  • une méthode qui dit si le carré est magique.

Exercice 3 : trouver tous les carrés magiques

On peut décomposer ce problème en deux étapes :

  • Considérer un ensemble de carrés qui inclut l’ensemble des carrés magiques
  • Parcourir cet ensemble et mémoriser dans une liste ceux qui sont magiques.

Exercice 4 : faire plus rapide

La vitesse de la fonction dépend de l’ensemble de départ qui peut contenir 9^9 possibilités, ou encore 9!.

  • A quoi correspondent ces nombres ?
  • Peut-on faire plus rapide encore ?