POO

Propriété

Une classe rectangle

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.

@property

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.

help

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)