Un premier code
On définit ci-dessous une classe modélisant un rectangle.
class Rectangle:
def __init__(self, largeur, longueur):
self.largeur = largeur
self.longueur = longueur
def __str__(self):
ch = "Le rectangle a une largeur égale à {}, ".format(self.largeur)
ch += "et une longueur égale à {}.".format(self.longueur)
return ch
r = Rectangle(12, 15)
print(r)
print()
r2 = Rectangle(15,12)
print(r2)
On obtient :
Le rectangle a une largeur égale à 12, et une longueur égale à 15.
Le rectangle a une largeur égale à 15, et une longueur égale à 12.
Le second affichage met en évidence un problème : rien n'empêche de créer un rectangle avec une largeur supérieure à la
longueur. Dans un programme court avec un seul programmeur, cela peut n'être pas gênant : le programmeur n'a qu'à faire attention !
Mais si le programme est plus important ou s'il y a plusieurs programmeurs ou si certains rectangles sont créés via des processus
plus complexes (comme résultat de fonctions...), le risque d'erreur devient inévitable.
On peut penser à corriger ainsi :
class Rectangle:
def __init__(self, largeur, longueur):
if largeur <= longueur :
self.largeur = largeur
self.longueur = longueur
else :
self.largeur = longueur
self.longueur = largeur
def __str__(self):
ch = "Le rectangle a une largeur égale à {}, ".format(self.largeur)
ch += "et une longueur égale à {}.".format(self.longueur)
return ch
r = Rectangle(12, 15)
print(r)
print()
r2 = Rectangle(15,12)
print(r2)
print()
r.largeur = 30
print(r)
qui donne :
Le rectangle a une largeur égale à 12, et une longueur égale à 15.
Le rectangle a une largeur égale à 12, et une longueur égale à 15.
Le rectangle a une largeur égale à 30, et une longueur égale à 15.
On voit que l'on a ainsi corrigé le problème à la création d'un objet de type Rectangle. Mais on n'est
toujours pas à l'abri d'une erreur lors de la modification d'un rectangle, comme le montre le dernier affichage.
Un setter
Une façon classique de corriger le problème est de modifier l'objet via l'utilisation d'une méthode.
On pourrait par exemple coder ainsi :
class Rectangle:
def __init__(self, largeur, longueur):
if largeur <= longueur :
self.largeur = largeur
self.longueur = longueur
else :
self.largeur = longueur
self.longueur = largeur
def setLargeur(self, nvLargeur):
if nvLargeur <= self.longueur:
self.largeur = nvLargeur
else:
self.largeur = self.longueur
self.longueur = nvLargeur
def __str__(self):
ch = "Le rectangle a une largeur égale à {}, ".format(self.largeur)
ch += "et une longueur égale à {}.".format(self.longueur)
return ch
r = Rectangle(12, 15)
print(r)
print()
r2 = Rectangle(15,12)
print(r2)
print()
r.setLargeur(30)
print(r)
qui donne :
Le rectangle a une largeur égale à 12, et une longueur égale à 15.
Le rectangle a une largeur égale à 12, et une longueur égale à 15.
Le rectangle a une largeur égale à 15, et une longueur égale à 30.
On voit que le problème est règlé.
Mais cette façon de faire ne nous met pas à l'abri d'une erreur de codage. Un second programmeur (ou le même programmeur
s'il reprend son code quelques mois plus tard en ayant oublié sa construction) pourra toujours écrire r.largeur = 50
provoquant à nouveau l'erreur de la création d'un rectangle de largeur supérieure à la longueur.
Accèder uniquement par les méthodes
Dans certains langages, les problèmes soulevés précédemment sont réglés en interdisant d'accèder directement aux attributs, obligeant
ainsi de passer par la fonction "setter" définie (par exemple en langage Java, l'attribut largeur sera déclaré private).
L'idée utilisée dans le langage python pour corriger cela est de faire en sorte que le code r.largeur
exécute en
fait une méthode.
Un code possible :
class Rectangle:
def __init__(self, dimension1, dimension2):
if dimension1 <= dimension2 :
self._largeur = dimension1
self._longueur = dimension2
else :
self._largeur = dimension2
self._longueur = dimension1
@property
def largeur(self):
return self._largeur
@property
def longueur(self):
return self._longueur
@largeur.setter
def largeur(self, nvLargeur):
if nvLargeur <= self._longueur:
self._largeur = nvLargeur
else:
self._largeur = self._longueur
self._longueur = nvLargeur
@longueur.setter
def longueur(self, nvLongueur):
if self._largeur <= nvLongueur:
self._longueur = nvLongueur
else:
self._longueur = self._largeur
self._largeur = nvlongueur
def __str__(self):
ch = "Le rectangle a une largeur égale à {}, ".format(self.largeur)
ch += "et une longueur égale à {}.".format(self.longueur)
return ch
r = Rectangle(12, 15)
print(r)
print()
r2 = Rectangle(15,12)
print(r2)
r.largeur = 30
print(r)
print("Aire du rectangle r : {}.".format(r.largeur * r.longueur))
r2._largeur = 100
print(r2)
On obtient :
Le rectangle a une largeur égale à 12, et une longueur égale à 15.
Le rectangle a une largeur égale à 12, et une longueur égale à 15.
Le rectangle a une largeur égale à 15, et une longueur égale à 30.
Aire du rectangle r : 450.
Le rectangle a une largeur égale à 100, et une longueur égale à 15.
Dans ce code, on "masque" les attributs en les faisant précédés d'un underscore. On peut encore accèder à ces attributs
_largeur
et _longueur
comme d'habitude (cf fin du code) mais cela n'a plus d'intérêt puisqu'on retomberait ainsi
dans les problèmes soulevés précédemment, comme le montre l'affichage du résultat suivant l'instruction r2._largeur = 100
.
Du point de vue du programmeur utilisateur de la classe,
les attributs ne sont pas _largeur
et _longueur
mais largeur
et longueur
.
- La fonction
largeur
qui est décorée par @property
définit le "getter", c'est à dire la valeur retournée
à chaque fois que l'on utilisera r.largeur
dans un calcul, un affichage...
- La fonction
largeur
qui est décorée par @largeur.setter
est la fonction "setter" : c'est elle qui sera appelée à chaque fois que l'on utilisera r.largeur
à gauche
dans une instruction d'affectation.
Ainsi, à la ligne 54 du code ci-dessus (print("Aire du rectangle r : {}.".format(r.largeur * r.longueur))
), ce sont
les fonctions "getter" (celles qui sont décorées par @property
) qui sont appelées. Alors qu'à la ligne 52
( r.largeur = 30
), c'est la fonction "setter" (celle décorée par @largeur.setter
) qui est appelée.
Nous avons écrit plus haut que les attributs du point de vue du codeur utilisateur de la classe sont largeur
et longueur
. Toutefois, il faut le faire savoir à ce programmeur ! En effet, s'il doit lire le code pour s'en servir,
le premier texte qu'il lit est la fonction constructeur __init__
et il pourrait être tenté d'utiliser
_largeur
et _longueur
détruisant ainsi les efforts d'encapsulation du programmeur de la classe.
En fait l'usage de l'underscore comme premier symbole des noms _largeur
et _longueur
est une
convention pour les programmeurs Python pour signaler qu'il s'agit là d'attributs à considérer avec le statut private (c'est à dire non
utilisable en dehors de la programmation de la classe elle-même).
Et par ailleurs, le programmeur de la classe doit renseigner le docstring de la classe qui permettra aux autres programmeurs de comprendre
ce à quoi ils sont censés avoir accès :
class Rectangle:
"""
Objet rectangle.
r = Rectangle(a,b)
définit un rectangle de largeur min(a,b) et de longueur max(a,b).
Attributs :
r.largeur : largeur du rectangle
r.longueur : longueur du rectangle
"""
def __init__(self, dimension1, dimension2):
if dimension1 <= dimension2 :
self._largeur = dimension1
self._longueur = dimension2
else :
self._largeur = dimension2
self._longueur = dimension1
@property
def largeur(self):
return self._largeur
@property
def longueur(self):
return self._longueur
@largeur.setter
def largeur(self, nvLargeur):
if nvLargeur <= self._longueur:
self._largeur = nvLargeur
else:
self._largeur = self._longueur
self._longueur = nvLargeur
@longueur.setter
def longueur(self, nvLongueur):
if self._largeur <= nvLongueur:
self._longueur = nvLongueur
else:
self._longueur = self._largeur
self._largeur = nvlongueur
def __str__(self):
ch = "Le rectangle a une largeur égale à {}, ".format(self.largeur)
ch += "et une longueur égale à {}.".format(self.longueur)
return ch
# fonction help, affichera le docstring de la classe :
help(Rectangle)
On obtient :
Help on class Rectangle in module __main__:
class Rectangle(builtins.object)
| Objet rectangle.
| r = Rectangle(a,b)
| définit un rectangle de largeur min(a,b) et de longueur max(a,b).
|
| Attributs :
| r.largeur : largeur du rectangle
| r.longueur : longueur du rectangle
|
| Methods defined here:
|
| __init__(self, dimension1, dimension2)
| Initialize self. See help(type(self)) for accurate signature.
|
| __str__(self)
| Return str(self).
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
|
| largeur
|
| longueur
(END)