OVH Cloud OVH Cloud

Prévenir de la modification d'un attribut

4 réponses
Avatar
Guillaume Bouchard
Bonjour à tous.

J'ai une classe qui contient un attribut. Si celui-ci est quelque chose
d'immutable, je peut trés facilement reagir à chaques modifications
(avec property()).

attribut = property(get_attribut,set_attribut)

Cependant si mon attribut est de type mutable (une liste par exemple).

Si celle-ci est modifiée en totalitée (remplacement de la liste), c'est
ma fonction set_attribut qui est appelée, je peut donc modifier ma
liste, faire mon traitement et rendre la main.

Mais si c'est la liste elle-meme qui est modifié, c'est get_attribut qui
est appelé, mais au moment de l'appel, la valeur est encore tel qu'avant :


class A(object):

def __init__(self):
self.bob = []

def get_bob(self):
print "Message = get bob"
print "Valeur de bob lors du get %s" % self.__bob
return self.__bob

def set_bob(self,bob):
print "Message = set bob"
self.__bob = bob

bob = property(get_bob,set_bob)




a = A() # Message = set bob
print "On commence"
>>> On commence
a.bob = "a b c".split() # Message = set bob
print a.bob # Message = get bob
# Valeur de bob lors du get ['a', 'b', 'c']
>>> ['a', 'b', 'c']
a.bob.append('5') # Message = get bob
# Valeur de bob lors du get ['a', 'b', 'c']

Bref, je n'ai aucun moyen de savoir si ma liste à été modifiée et encore
moins de moyen de lancer ma fonction de traitement sur la nouvelle liste
(à part en créant un timeout qui lancera la fonction juste après que bob
ai été modifié, mais c'est plus crade que possible)

J'ai donc deux solutions qui s'offrent à moi:

1) que bob herite d'une classe qui soit relativement proche d'une liste,
mais avec toutes les méthodes redefinies pour envoyer un message à
chaques modifications
2) utiliser les GObjects, sympa, cela fonctionne bien mais je ne veux
pas être dependant d'une lib externe rien que pour cela.
3) ... ?


Vos avis ?

--
Guillaume.

4 réponses

Avatar
laurent
Bonjour.
C'est intéressant de vouloir faire ça.
J'ai déjà penser un truc comme ça qui permettrait de tracker par
exemple les modifs sur un model donné. Ca correspond pas mal à un
patron de conception connu, l'observeur .

Ce que tu pourrais faire c'est peut-être de dire à l'utilisateur de
ta classe d'utiliser tes méthodes de modifications pour cette liste
class Person:
def __init__(self):
self._contacts = []
def addContact(self, contact):
# du coup ici, tu fais ce que tu veux
self._contacts.append(contact)

Sinon ton idée de faire un Wrapper est plutot bonne. UserList est
justement une classe de python prévue pour faire ce genre de chose. Et
tu as la même chose pour les chaîne de caractère et pour les dico.
Ensuite il faut redéfinir les méthodes de modifications et d'acces
from UserList import UserList
class MaListe(UserList):
def append(self, element):
# du coup ici, tu fais ce que tu veux :)
UserList.append(element)
.....
Il seraît peut être plus judicieux de modifier directement les
méthodes à surcharge telles :
__getitem__, __setitem__, etc....

et dès que t'as besoin de ce wrapper tu l'intancie gentiment comme ca

list = MaListe([])
ou mieux faire ça :

class Person:
def __init__(self):
self._contacts = []
def getContacts(contact):
return MaListe(self._contacts)

sinon tu peux aussi penser à rajouter par dessus le mécanisme des
annotations apparue dans la version 2.4 de python.

Laurent
Avatar
pbouige
sinon tu peux aussi penser à rajouter par dessus le mécanisme des
annotations apparue dans la version 2.4 de python.


c'est quoi "le mécanisme des annotations" ?

merci de nous en dire plus :-)

Avatar
Do Re Mi chel La Si Do
Bonjour !

Peut-être Laurent veut-il parler des decorators. Dans ce cas, ce serait une
traduction qui en vaudrait une autre....

@-salutations

Michel Claveau
Avatar
Guillaume Bouchard
laurent wrote:
Bonjour.
C'est intéressant de vouloir faire ça.


En effet :)

Ce que tu pourrais faire c'est peut-être de dire à l'utilisateur de
ta classe d'utiliser tes méthodes de modifications pour cette liste
class Person:
def __init__(self):
self._contacts = []
def addContact(self, contact):
# du coup ici, tu fais ce que tu veux
self._contacts.append(contact)


Cette technique m'irais bien, cependant il est trés difficile en python
de contraindre un utilisateur puisqu'il n'y a pas de notions de
privée/publique.

De plus je trouve plus naturelle de faire maobject.maliste.append() que
monobject.appendintomaliste()


Sinon ton idée de faire un Wrapper est plutot bonne. UserList est
justement une classe de python prévue pour faire ce genre de chose.


Ou directement list :)

Voici ce que cela donne chez moi. Noter que update est une fonction
passée en parametre de ma liste qu'elle appele après toutes modification.

---------------------
class SpecialList(list):
def __init__(self,update,*args):
list.__init__(self,*args)
self.update = update
def __delitem__(self,*args):
ret = list.__delitem__(args)
self.update()
return ret
def __delslice__(self,*args):
ret = list.__delslice__(self,*args)
self.update()
return ret
def append(self,*args):
ret = list.append(self,*args)
self.update()
return ret
def extend(self,*args):
ret = list.extend(self,*args)
self.update()
return ret
def insert(self,*args):
ret = list.insert(self,*args)
self.update()
return ret
def pop(self,*args):
ret = list.pop(self,*args)
self.update()
return ret
def remove(self,*args):
ret = list.remove(self,*args)
self.update()
return ret
def reverse(self,*args):
ret = list.reverse(self,*args)
self.update()
return ret
def sort(self,*args):
ret = list.sort(self,*args)
self.update()
return ret
def __setitem__(self,*args):
ret = list.__setitem__(self,*args)
self.update()
return ret
------------------------------

Cependant je cherche à faire cela de façon plus propre, j'aimerais bien
faire une boucle sur la liste des methodes qui m'interesse et les
modifier à la volée. Cela donnerais quelque chose du genre

----------------------------------
class SpecialList2(list):

decorate = """__delitem__ __delslice__ __setitem__
__setslice__
append extend insert pop remove
reverse sort""".split()

def decorator(self,func):
def _(*args):
ret = func(*args)
self.update()
return ret
return _

def __init__(self,update,*args):

for i in self.decorate:
setattr(self,i,self.decorator(getattr(self,i)))


list.__init__(self,*args)
self.update = update
--------------------------------

Cependant le comportement est bizarre:

def test():
print 'bob'

a = SpecialList2(test)
a.append(5)
'bob'




a[0] = 25
a.__setitem__(0,25)
'bob'




Apparament a[0] et a.__setitem__() ne passent pas par la meme chose.
J'ai raté quoi ?



sinon tu peux aussi penser à rajouter par dessus le mécanisme des
annotations apparue dans la version 2.4 de python.


C'ets un peut ce que je fais dans ma derniere methode, mais cela a un
effet secondaire embetant, pickle ne peut pas utiliser ma classe
SpecialList2 du fait de la redefinition dynamique des methodes.

Peut-être une metaclass m'aiderait, mais c'est un domaine encore sombre
à mes yeux.

--
Guillaume.