classe liste avec alerte en cas de modification [résolu]

classe liste avec alerte en cas de modification [résolu] - Python - Programmation

Marsh Posté le 27-09-2010 à 10:43:29    

Bonjour à tous et à toutes,
 
j'ai besoin d'une classe dérivée de list qui puisse appeler une fonction d'alerte à chaque fois que l'objet est modifié; quelque chose comme :

Code :
  1. l = MaListe()
  2. >>> "objet modifié"
  3. l[2] = 3
  4. >>> "objet modifié"
  5. etc...


 
J'ai donc écrit ceci : une classe dérivée de list qui redéfinit TOUTES les fonctions associées à l'objet list avec un appel à la fonction d'alerte à la fin du travail :

Code :
  1. class L(object):
  2.     def __init__(self, args, func):
  3.         list.__init__(args)
  4.         self.func = func
  5.         # appel à la fonction d'alerte :
  6.         self.func()
  7.     def append(self, object):
  8.         list.append(object)
  9.         # appel à la fonction d'alerte :
  10.         self.func()


 
Le tout fonctionne bien mais m'oblige à redéfinir un grand nombre de fonctions (__add__, __iadd__, ..., insert, etc.). Je me demandais s'il existait un moyen plus rapide et plus élégant ?
 
Merci d'avance !


Message édité par suizokukan le 29-09-2010 à 08:03:15

---------------
rule #1 : trust the python
Reply

Marsh Posté le 27-09-2010 à 10:43:29   

Reply

Marsh Posté le 27-09-2010 à 14:07:06    

http://docs.python.org/reference/d [...] ttribute__

 

Mais ça fait pas la différence entre un append qui modifie ta liste et un __getitem__ qui ne la modifie pas, et ça va flinguer tes perfs.

 

Pourquoi tu veux faire ce truc?


Message édité par masklinn le 27-09-2010 à 14:07:15

---------------
Stick a parrot in a Call of Duty lobby, and you're gonna get a racist parrot. — Cody
Reply

Marsh Posté le 27-09-2010 à 15:30:34    

Merci Masklinn pour ta réponse; je vais examiner ton lien pour en comprendre le contenu.
J'ai besoin d'une liste d'objets, liste qui doit être triée de manière complexe; pour éviter d'oublier de trier ma liste et d'utiliser une liste non triée, je voulais appeler ma fonction tri à chaque fois que je modifiais ma liste.

 

Mais j'ai l'impression de réinventer la roue et je trouve mon code crade...

 

Une meilleure idée ?

 

NB : au fait, je travaille avec Python 3.

Message cité 1 fois
Message édité par suizokukan le 27-09-2010 à 15:44:41

---------------
rule #1 : trust the python
Reply

Marsh Posté le 27-09-2010 à 16:00:47    

En adoptant l'idée de Masklinn, voici le code que j'obtiens; pour le moment, l'objet ne "réagit" qu'aux appels à append et pop, mais c'est pour l'exemple :
 

Code :
  1. class L(list):
  2.     def __init__(self, arg, fonction_à_appeler):
  3.         self.fonction_à_appeler = fonction_à_appeler
  4.        
  5.         list.__init__(self,arg)
  6.         # alerte : l'objet a été modifié :
  7.         self.alerte_modification()
  8.     def alerte_modification(self):
  9.         if self.fonction_à_appeler!=None:
  10.             self.fonction_à_appeler()
  11.     def __getattribute__(self, arg):
  12.         if arg in ("append","pop" ):
  13.             self.fonction_à_appeler()
  14.            
  15.         return list.__getattribute__(self,arg)


 
Bon, c'est moche mais ça marche. Un souci : les fonctions spéciales comme __iadd__ ne semblent pas attrapées. C'est bien ce qui est dit dans la doc :

This method may still be bypassed when looking up special methods as the result of implicit invocation via language syntax or built-in functions.

(http://docs.python.org/py3k/reference/datamodel.html)
 
Ce qui me surprend est que personne(?) n'a l'air d'avoir rencontré le même problème que moi. Est-ce que je fais fausse route ?


Message édité par suizokukan le 27-09-2010 à 16:09:48

---------------
rule #1 : trust the python
Reply

Marsh Posté le 27-09-2010 à 16:08:22    

suizokukan a écrit :

Mais j'ai l'impression de réinventer la roue et je trouve mon code crade...
 
Une meilleure idée ?


Un tri quand tu as besoin d'utiliser ta liste (d'autant plus si l'insertion est plus fréquente que la lecture), ou bien alors une liste naturellement triée (il n'y en à ma connaissance pas dans la stdlib), ou le discutablement nommé bisect qui permet d'insérer une nouvelle valeur à sa place (triée) dans la liste.
 
Regardes aussi du côté de heapq.


---------------
Stick a parrot in a Call of Duty lobby, and you're gonna get a racist parrot. — Cody
Reply

Marsh Posté le 27-09-2010 à 17:21:46    

Un grand merci à Masklinn pour m'avoir fait découvrir bisect et heapq; pour le moment j'aimerais éviter de les utiliser (j'ai un problème pour personnaliser la comparaison entre mes objets).

 

Je crois que j'ai un début de solution à mon problème; c'est pas super propre mais je pense que c'est acceptable (Masklinn, ne t'énerve pas, question perf' ça doit en effet être affreux). En particulier j'attrape les appels aux fonctions spéciales (__add__, ...)

 
Code :
  1. import collections
  2. class L(collections.UserList):
  3.     def __init__(self,arg):
  4.         collections.UserList.__init__(self,arg)
  5.     def __getattribute__(self,arg):
  6.         if arg=="data":
  7.             self.fonction_d_alerte()
  8.         return collections.UserList.__getattribute__(self,arg)
  9.     def fonction_d_alerte(self):
  10.         print("lecture ou écriture dans .data" )
 

L'idée est de détecter la lecture ou l'écriture dans .data qui est hérité de UserList; bien sûr je détecte pêle-mêle des modifications et de simples lectures mais je suis ainsi sûr de ne pas rater une écriture dans mon objet.

 

Je suis toujours preneur d'idées meilleures. Merci de m'avoir lu !

Message cité 1 fois
Message édité par suizokukan le 27-09-2010 à 17:29:58

---------------
rule #1 : trust the python
Reply

Marsh Posté le 27-09-2010 à 17:42:13    

suizokukan a écrit :

Un grand merci à Masklinn pour m'avoir fait découvrir bisect et heapq; pour le moment j'aimerais éviter de les utiliser


Pourquoi?

suizokukan a écrit :

Je crois que j'ai un début de solution à mon problème; c'est pas super propre mais je pense que c'est acceptable (Masklinn, ne t'énerve pas, question perf' ça doit en effet être affreux). En particulier j'attrape les appels aux fonctions spéciales (__add__, ...)
 

Code :
  1. import collections
  2.  
  3. class L(collections.UserList):
  4.  
  5.    def __init__(self,arg):
  6.        collections.UserList.__init__(self,arg)
  7.  
  8.    def __getattribute__(self,arg):
  9.        if arg=="data":
  10.            self.fonction_d_alerte()
  11.        return collections.UserList.__getattribute__(self,arg)
  12.  
  13.    def fonction_d_alerte(self):
  14.        print("lecture ou écriture dans .data" )




 [:psywalk]  
 
1. Tu peux hériter de `list`, pas besoin d'hériter de `UserList`. D'ailleurs je sais même pas d'où tu l'as sorti, je l'ai plus dans Python 2.6 [:pingouino]
2. Le code n'a aucun rapport avec la description d'en dessous, c'est normal docteur?
3. Pourquoi ne pas juste décorer une instance de `list`, si tu veux vraiment pas utiliser une collection faite pour?


---------------
Stick a parrot in a Call of Duty lobby, and you're gonna get a racist parrot. — Cody
Reply

Marsh Posté le 27-09-2010 à 18:01:23    


> Pour de mauvaises raisons : je n'arrive pour l'instant pas à les utiliser. Mais j'y viendrai, c'est juste une question de temps.
 

Citation :

1. Tu peux hériter de `list`, pas besoin d'hériter de `UserList`. D'ailleurs je sais même pas d'où tu l'as sorti, je l'ai plus dans Python 2.6


UserList est encore dans la doc' de Python3 (http://docs.python.org/py3k/librar [...] s.UserDict). Bon, ce n'est pas un argument en soi...
 

Citation :

2. Le code n'a aucun rapport avec la description d'en dessous, c'est normal docteur?


> désolé, j'ai moi-même du mal à comprendre ce que je code.
 

Citation :

3. Pourquoi ne pas juste décorer une instance de `list`, si tu veux vraiment pas utiliser une collection faite pour?


> si j'utilise juste une instance de list (appelons-la data), je perds la possibilité d'écrire : l.append(...) et je devrai écrire l.data.append(...)
> si je fais dériver ma classe L de list, j'ai mon L.__getattribute__ qui ne détecte plus la lecture/modification de l'objet via les fonctions spéciales (__add__, ...)
 
Désolé d'être aussi peu clair : merci pour ta patience.
Bon, je reprendrai cela un peu plus tard. Excusez-moi pour le bordel, mais j'ai une réunion.


---------------
rule #1 : trust the python
Reply

Marsh Posté le 27-09-2010 à 18:20:22    

Citation :

> si j'utilise juste une instance de list (appelons-la data), je perds la possibilité d'écrire : l.append(...) et je devrai écrire l.data.append(...)


Non, tu réimplémentes les accès que tu veux intercepter dans le décorateur et tu forward le reste à coup de __getattr__:

Code :
  1. class LoggingList:
  2.    def __init__(self, iterable=None):
  3.        self.lst = list(iterable) if iterable else []
  4.    def append(self, value):
  5.        # zomg edition
  6.        return self.lst.append(value)
  7.    def extend(self, iterable):
  8.        # zomg edition
  9.        return self.lst.extend(iterable)
  10.    def insert(self, index, value):
  11.        # zomg edition
  12.        return self.lst.insert(index, value)
  13.    # idem pour pop, remove, reverse et sort, ainsi que __iadd__ je présume (et ptet __add__ et __radd__ mais bon...)
  14.    def __getattr__(self, attr):
  15.        return getattr(self.lst, attr)
 

et voilà, c'est plus long mais c'est clair et clean. Si besoin tu peux même te créer une fonction ou un descripteur qui va bien pour faire une partie du boulot pour quoi.


Message édité par masklinn le 27-09-2010 à 18:20:36

---------------
Stick a parrot in a Call of Duty lobby, and you're gonna get a racist parrot. — Cody
Reply

Marsh Posté le 28-09-2010 à 18:32:36    

Merci Masklinn pour ta dernière réponse; il m'a fallu du temps pour la digérer.
 
Si je comprends bien, "ta" classe LoggingList oblige :
- à définir les fonctions qui modifient l'objet : append, extend...
- à définir les fonctions spéciales, qu'elles modifient ou non l'objet : __getitem__, __setitem__ (ces fonctions ne sont pas attrapées par __getattr__)
Par contre d'autres fonctions (comme count) ne sont pas à écrire; elles passent par __getattr__ qui les renvoient au type list.
 
Pour simplifier le code, pourquoi ne pas faire hériter LoggingList du type list ? Il faudrait alors :
- à définir les fonctions qui modifient l'objet : append, extend...
Par contre aucune autre fonctions n'est à réécrire; les autres fonctions (spéciales ou non) passent directement par le type list.
 
On pourrait avoir quelque chose comme :

Code :
  1. class LoggingList(list):
  2.    
  3.    def __init__(self, iterable=None):
  4.        list.__init__(self,iterable)
  5.        
  6.    def append(self, value):        
  7.        fonction_d_alerte()  # <- appel à une fonction d'alerte quelconque
  8.        return list.append(self,value)


 
Bon, je sais que je reviens à l'un de tes conseils de départ (avec la classe héritée de list); désolé d'avoir mis du temps à comprendre.


---------------
rule #1 : trust the python
Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed