Magazine

python et les descripteurs

Publié le 02 mai 2008 par Mikebrant

ou descriptors.

Qu'est-ce qu'un descripteur ?

Un descripteur est un objet qui surcharge au moins une de ces trois méthodes:


__get__(
self,instance,classe) : est appelée quand on récupère la valeur de l'attribut.
instance désigne l'instance dans laquelle se situe l'attribut.
classe désigne l'instance dans laquelle se situe l'attribut.
L'un des deux vaudra donc none.


__set__(self,iinstance,valeurObjet) : est appelée quand on donne une valeur à l'attribut.
instance désigne l'instance dans laquelle se situe l'attribut.


__delete__(self,instance) : est appelé quand on veut supprimer l'attribut.
instance désigne l'instance dans laquelle se situe l'attribut.

Mais à quoi ca sert ? d'être quelqu'un d'autre  c'est d'un ordinaire d'une histoire à l'autre...

Prenons maVariable, variable d'instance de monObjet .

Quand on veut accéder à la valeur de maVariable ,on va en réalité chercher dans le dictionnaire de monObjet [ puis de type(monObjet),etc... ] sa valeur .

Comme je ne connaissais pas ce fameux dictionnaire( et encore moins la facon dont les accès aux attributs se faisaient), vous avez le droit à un tout petit exemple qui va juste afficher le dictionnaire de monObjet, qui stocke toutes ses variables(et leurs valeurs) :

#-*- coding:Utf-8 -*-
class MaClasse:
  def __init__(self):
   self.un = 2
   self.trois = 4
monObjet = MaClasse()
print monObjet.__dict__

Donc, finalement si on veut récupérer la valeur de maVariable, on a cette affection:

valeurmaVariable = monObjet.__dict__['maVariable'] ou encore:
valeurmaVariable = type(monObjet).__dict__['maVariable']

Et bien, maintenant si maVariable est un descripteur, quand on va vouloir accéder à sa valeur, on va d'abord lancer la méthode __get__()  puis renvoyer la valeur:

valeurmaVariable = monObjet.__dict__['maVariable'].__get__() ou encore:
valeurmaVariable = type(monObjet).__dict__['maVariable'].__get__()

Et donc grâce à de tels mécanismes on va pouvoir effectuer des pré-traitements.

Rien de tel qu'un exemple pour mieux comprendre:

#-*- coding:Utf-8 -*-
class MonDescripteur(object):
  
   def __init__(self,valeur,nom,typeObjet):
   self.valeur = valeur
   self.nom=nom
   self.typeObjet=typeObjet
  
   def __get__(self,instance,classe):
   print("on entre dans le get() de "+ self.nom)
   return self.valeur
   def __set__(self,instance,valeurObjet):
   try:
   print("on entre dans le set() de "+ self.nom)
   self.valeur=self.typeObjet(valeurObjet)
   #on caste la valeur que l'on veut affecter à notre objet
   #selon le type que l'on a autorisé à son initialisation
   except:
   print('mauvaise valeur')
   return self.valeur
   #si le cast à échoué
   else:
   print('bonne valeur')
   return self.valeur
  
class MaClasse(object):
   monAttribut=MonDescripteur(69,"monAttribut",float)  
monObjet=MaClasse()
monObjet.monAttribut="je mets une chaine au lieu d'un float"
print monObjet.monAttribut

Ici,
on crée notre classe MonDescripteur, laquelle hérite de object afin d'être new-style .

MonDescripteur prend 3 arguments en paramètres:la valeur, le nom de notre objet et son type .
En effet, le but de cette classe est que chacune de ses instances ne peut se voir affecter qu'un seul même type de valeur(nom sert juste de formatage,comme vous pouvez le voir).

Enfin dans MaClasse on a seulement une variable de classe, qui va être notre objet descripteur.

Voilà, voilà, vous pouvez admirez le résultat.

Bon, vu qu'il y a un film pourri sur un phoque, on va continuer.

Le billet précédent portait sur les properties : property(fget=None,fset=None,fdel=None,doc=None).
Et bien je suis sûr que vous avez remarquez cette ressemblence frappante entre les property et les descripteurs.
C'est peut-être que la classe property est basée dessus ?

Alors maintenant que les descripteurs ne sont plus un secret pour nous, on va réécrire la classe property (enfin on va essayer hein):

#-*- coding:Utf-8 -*-
class Property(object):
   def __init__(self,fget=None,fset=None,doc=None):
   self.fget=fget
   self.fset=fset

   self.__doc__=doc
   ###pas bien compliqué jusque là #####
   #je rappelle que fget et fset font références à des fonctions
  
   def __get__(self,instance,classe):
   print("on entre dans le __get__()")
   if self.fget is not None:
   return self.fget(instance)
  
   def __set__(self,instance,valeurObjet):
   print("on entre dans le __set__()")
   if self.fset is not None:
   return self.fset(instance,valeurObjet)
  
class Test(object):
  
   def __init__(self):
   self._x=10
  
   def get_X(self):
   return self._x
  
   def set_X(self,valeur):
   self._x=valeur
  
   x=Property(get_X,set_X,None,None)
hop=Test()
hop.x=3
print hop.x

Ici,
dans Property on surcharge donc nos trois méthodes, enfin deux, j'ai zappée  __del__():

__get__(self,instance,classe): Si  self.fget  ne vaut pas None, on la retourne.
__self__(self,instance,maValeur):Si self.fset ne vaut pas None, on la retourne, comme au dessus.

Voilà, voilà, on a créé notre Property (enfin à moitié) . C'est pas bien compliqué et assez intéressant.

J'espère que cette intro aux descripteurs, vous a plu.


Retour à La Une de Logo Paperblog

A propos de l’auteur


Mikebrant 9 partages Voir son profil
Voir son blog

l'auteur n'a pas encore renseigné son compte l'auteur n'a pas encore renseigné son compte

Dossier Paperblog