Bonjour, J'ai un objet que l'utilisateur pourra sauvegarder
J'aimerais pouvoir détecter systématiquement quand certaines modifications sont effectuées sur l'objet, par exemple pour que le titre de la fenêtre principale ajoute un petit "*" à la fin tant que l'objet n'as pas été sauvegardé, comme dans Notepadd++
j'ai d'abort pensé à utiliser __setattr__ ou __setattribut__. Je suis tombé sur la page de sam & max sur les descripteurs et les pattern observers qui semblent être exactement le genre de truc donc j'avais besoin (Avertissement au cas où: leur blog est très pédagogique, mais leurs humour n'est pas forcément tout public.)
Dans les deux cas, le seul problème c'est que les attributs que contient mon objet, sont… une liste et un dictionnaire.
Et là, même le code de S&M ne fonctionne pas:
>>> j.credits
Les crédits ont été consultés:
get <__main__.Joueur object at 0x7fdc351512e0> credits 0 None
0
>>> j.credits = 5
Les crédits ont changé:
set <__main__.Joueur object at 0x7fdc351512e0> credits 0 5
>>> j.credits = [1]
Les crédits ont changé:
set <__main__.Joueur object at 0x7fdc351512e0> credits 5 [1]
>>> Les crédits ont été consultés:
get <__main__.Joueur object at 0x7fdc351512e0> credits [1] None
j.credits.append("Hop, j'appelle… __get__ !") # Là, ça ça marche pas.
Les crédits ont été consultés: # Non, gros, ils n'ont pas été seulement consultés, ils ont été modifiés !
get <__main__.Joueur object at 0x7fdc351512e0> credits [1] None
>>>
En effet, lorsque je fait j.credits[index] = [mettez ce que vous voulez ici] ou j.credits.append(de même ici). Ce n'est pas la méthodes __set__ de l'attribut "crédit" qui le modifie. C'est la méthode __get__ qui retourne l'attribut qui, en tant qu'objet se modifie lui même à l'aide d'une de ses propres méthodes…
Du coup, quelqu'un connait-il une solution pour déclencher une fonction a chaque fois que l'attribut est modifié, même lorsque cet attribut est une liste ou un dictionnaire ?
J'ai pensé hériter les classes de mes attributs en rajoutant par exemple un décorateur à toute les méthodes qui sont utilisées pour la modification de l'attribut (__set__, __setitem__, __delitem__, __add__, yen a toute une tripotée) ou même les overrider directement dans le constructeur de l'objet (self.attribut.__set__ = mondécorateur(self.attribut.__set__) . Mais ça ne me plaît pas. c'est verbeux. Il y a un risque que j'oublie d'overrider une méthode alors que le but de la manœuvre est justement d'éliminer le risque d'oubli lors d'un appel manuel. Pas ouf. J'ai pas d'idée qui me paraisse vraiment élégante.
C'est quoi qui va pas avec object.__setattr__ ? Ou bien je n'ai pas compris la question ?
edit:
je vois, à cause des itérables ...
class Foo():
def __init__(self):
self.list = []
self.dict = {}
def __getattribute__(self,attr):
print(f"l'attribut <{attr}> a été accédé")
if attr in ('list','dict'):
print("peut-être faudra-t-il sauvegarder")
return object.__getattribute__(self,attr)
def __setattr__(self,attr,newValue):
if attr in object.__getattribute__(self,'__dict__').keys():
lastValue = object.__getattribute__(self,attr)
if lastValue != newValue:
print(f"l'attribut <{attr}> a été modifié")
else:
print(f"l'attribut <{attr}> a été créé")
object.__setattr__(self,attr,newValue)
a = Foo()
a.bar = 1
a.bar = 2
a.list.append(5)
Non, __setattr__ n'est pas appelé quand l'attribut est modifié par une de ses propres méthodes.
sedawk a écrit:
À ta place je me contenterai de bannir tous les types mutables parce que vouloir gérer les modification des types mutables me parait très difficile (je ne vois même pas comment le faire bien en fait).
En container immutable et indexable, il reste le tuple si je ne me trompe pas. Et du coup, faudrait que je rajoute des méthodes pour le modifier dans ma classe. Faut voir si se priver de la souplesse des listes est suffisamment confortable dans mon cas ou si ça implique trop de galères. Je vais tester ça.
josmiley a écrit:
autre piste, créer ses propres lists et dicts
Ça implique de devoir réécrire toute les méthodes dont j'aurai besoin. Quitte à devoir faire un truc sur toutes les méthodes de la classe list que j'utilise, je trouve plus simple de toutes les décorer.
class une_classe:
def __init__(self):
self.mon_attribut = list()
self.mon_attribut.__set__ = self.detect_call(self.mon_attribut.__set__)
self.mon_attribut.__setitem__ = self.detect_call(self.mon_attribut.__setitem__)
self.mon_attribut.__add__ = self.detect_call(self.mon_attribut.__add__)
#… etc. Déjà suffisamment lourd, alors s'il faut carrément recréer la classe entière…
def detect_call(self, fonction):
def send_signal(*args, **kwargs):
[…] # du code
print("signal envoyé")
return fonction(*args, **kwargs)
return send_signal
Le seul risque étant que le signal peut être lancer deux fois (par exemple si __add__ fait lui-même appel à la fonction __setitem__)
Bon merci. En tout cas j'ai l'impression qu'il n'y a pas de réponse simple.
Ça implique de devoir réécrire toute les méthodes dont j'aurai besoin.
meuh non, tu remplaces juste tes list par des MyList et tes dict par des MyDict; héritant de list et dict, aucune méthode n'est à réécrire. Tu as essayé le code que j'ai posté ?
Celui qui trouve sans chercher est celui qui a longtemps cherché sans trouver.(Bachelard) La connaissance s'acquiert par l'expérience, tout le reste n'est que de l'information.(Einstein)
Il y aurait pas deux signaux ? un pour la liste et l'autre pour le dictionnaire ?
Dans l'absolu, si. Mais si je trouve une méthode pour l'un, à priori elle marchera sur l'autre, non ?
fred1599 a écrit:
Sur quelle méthodes appelées de l'un et de l'autre objets pour que cette détection soit prise en compte ?
C'est toute la question. Je veux détecter les modifications. Donc, soit il faut modifier toutes les méthodes s'occupant de modifier c'est objets. Soit il faut detecter automatiquement si une méthode modifie l'objet ou pas. Enfin, pour le moment je ne vois que ça.
fred1599 a écrit:
Peut-être voir du côté d'un dictionnaire modifiants ces deux objets, le hash change...
J'avais déjà pensais a une comparaison avec une sorte de cache, enfin un truc style ma_liste == old_liste. Le hash c'est la version ++.
Mais il faut quand même un truc qui déclenche la comparaison. Peut-être en comparant à chaque __getattr__ ? Oui ça doit marcher ça… Je teste et te redis.
josmiley a écrit:
meuh non, tu remplaces juste tes list par des MyList et tes dict par des MyDict; héritant de list et dict, aucune méthode n'est à réécrire. Tu as essayé le code que j'ai posté ?
Pardon, je suis effectivement passé un peu vite sur ton code. Du coup, ça marche presque:
>>> f = Foo()
>>> f.list
[1, 2, 3, 4, 5]
>>> méthode susceptible d'être appliquée: append
sauvegarde effectuée
méthode susceptible d'être appliquée: __class__
sauvegarde effectuée
f.list.append(7) # ça, ça marche. Même un peu trop.
méthode susceptible d'être appliquée: append
sauvegarde effectuée
>>> del f.list[2] # Ça, ça marche pas.
>>> f.list[2] = 6 # Ça, ça marche.
item susceptible d'être modifié: [2] = 6
sauvegarde effectuée
>>> for el in f.list:
print(el)
1
2
6
5
7
>>> for el in f.list: # Et là, ça marche pas non plus.
el = 5
Et effectivement, del passe au travers ... rajout de __delitem___ dans Bar.
class Bar():
def __getattribute__(self,attr):
if callable(object.__getattribute__(self,attr)):
print("méthode susceptible d'être appliquée:",attr)
print('sauvegarde effectuée\n')
return object.__getattribute__(self,attr)
def __setitem__(self,key,value):
print(f"item susceptible d'être modifié: [{key}] = {value}")
print('sauvegarde effectuée\n')
super(Bar,self).__setitem__(key,value)
def __delitem__(self,key):
print(f"la clé <{key}> va être supprimée")
super(Bar,self).__delitem__(key)
- Edité par josmiley il y a 24 minutes
Effectivement, autant pour moi. Du coup ta solution a l'air de marcher. J'essaierai de mieux la comprendre à une heure raisonnable demain ou après-demain.
sedawk a écrit:
Et que feras-tu si le dictionnaire ou la liste contient d'autres mutables ? Le problème est bien plus complexe qu'il n'y parait.
Mmm, j'y avais pas pensé. En l’occurrence, dans mon cas il s'agira d'une liste d'objet customs. Du coups, même la solution de remplacer la liste par un tuple ne suffira pas.
Il y aurait pas deux signaux ? un pour la liste et l'autre pour le dictionnaire ?
Dans l'absolu, si. Mais si je trouve une méthode pour l'un, à priori elle marchera sur l'autre, non ?
Non c'est deux objets différents... En plus de cela, dans un dictionnaire on peut modifier/ajouter/supprimer clés et valeurs
Megalo a écrit:
fred1599 a écrit:
Sur quelle méthodes appelées de l'un et de l'autre objets pour que cette détection soit prise en compte ?
C'est toute la question. Je veux détecter les modifications. Donc, soit il faut modifier toutes les méthodes s'occupant de modifier c'est objets. Soit il faut detecter automatiquement si une méthode modifie l'objet ou pas. Enfin, pour le moment je ne vois que ça.
Ça me semble plutôt inquiétant, à mon sens c'est à toi d'y répondre en sachant déjà à l'avance si tu utiliseras la méthode append des listes, ou la méthode update des dictionnaires par exemple. On pourrait croire que tu as en tête une conception qui te permettrait de répondre à ces questions.
Megalo a écrit:
fred1599 a écrit:
Peut-être voir du côté d'un dictionnaire modifiants ces deux objets, le hash change...
J'avais déjà pensais a une comparaison avec une sorte de cache, enfin un truc style ma_liste == old_liste. Le hash c'est la version ++.
Mais il faut quand même un truc qui déclenche la comparaison. Peut-être en comparant à chaque __getattr__ ? Oui ça doit marcher ça… Je teste et te redis.
J'ai pas tout compris, mais effectivement, la création d'un objet avec son hash renseigné paraît évident, pour comparer avec le hash d'un objet modifié.
Celui qui trouve sans chercher est celui qui a longtemps cherché sans trouver.(Bachelard) La connaissance s'acquiert par l'expérience, tout le reste n'est que de l'information.(Einstein)
Ça me semble plutôt inquiétant, à mon sens c'est à toi d'y répondre en sachant déjà à l'avance si tu utiliseras la méthode append des listes, ou la méthode update des dictionnaires par exemple. On pourrait croire que tu as en tête une conception qui te permettrait de répondre à ces questions.
Ben justement, l'idée c'était de détecter les modifications, indépendamment du fait que ce soit la méthode toto ou tintin qui ait modifié l'objet en question. Le principe de l'encapsulation quoi. On crée une classe, et une fois celle-ci crée, l'utilisateur peut manipuler les instances comme bon lui semble sans avoir à se préoccuper d'utiliser telle ou telle méthode.
En C++, je pense que le problème aurait été réglé en quelques lignes en surchargeant le getter (si la version const est appelé, ya rien à faire, si c'est la non-const, alors il y a eu modification de l'attribut). En Python la notion de méthode const n'existe pas. Le prix de la simplicité probablement.
fred1599 a écrit:
J'ai pas tout compris, mais effectivement, la création d'un objet avec son hash renseigné paraît évident, pour comparer avec le hash d'un objet modifié.
Ben, c'est simple. Comparer les hash permet de détecter si l'objet a changé au moment où tu fais la comparaison. Mais le test ne se fait pas automatiquement à chaque fois que tu fais quelques chose susceptible de modifier l'objet. Donc tu reportes le problème "détecter une modification" à "détecter une possible modification."
Le mieux que j'ai réussi à faire jusqu'à présent:
class MaClasse:
def __init__(self):
self._maliste = list()
self._mondic = dict()
self._hash = hash(self)
@property
def maliste(self):
if self._hash != hash(self):
print("maliste modified")
self._hash = hash(self)
return self._maliste
@maliste.setter
def maliste(self, liste):
print("maliste modified")
self._maliste = list(maliste)
self._hash = hash(self)
@property
def mondic(self):
if self._hash != hash(self):
print("mondic modified")
self._hash = hash(self)
return self._mondic
@mondic.setter
def mondic(self, dic):
print("mondic_modified")
self._mondic = dict(dic)
self._hash = hash(self)
def __hash__(self):
h1 = (r for r in self._maliste)
h2 = (i for i in self._mondic.items())
return hash(tuple(itertools.chain(h1, h2)))
Le problème est que le
if self._hash != hash(self):
dans le getter, est appelé avant le return. Hors, les méthodes de maliste ne modifient l'objet qu'au moment du return du getter. Dis autrement, la détection ne se fait qu'à l'appel suivant si appel il y a.
Il faudrait donc pour voir lancer un équivalent de
Ce que tu veux ressemble au pattern state où je verrai tes objets soit dans l'état change ou no change
Un exemple ICI... qui peut te donner une idée peut-être.
Celui qui trouve sans chercher est celui qui a longtemps cherché sans trouver.(Bachelard) La connaissance s'acquiert par l'expérience, tout le reste n'est que de l'information.(Einstein)
Merci, mais sauf erreur de ma part, ton exemple ne fait que reporter le problème: qu'est-ce qui va appeler la fonction switch() ou change() à chaque changement d'état de l'objet ?
Non, pour le moment je pense que je vais partir sur autre chose. Je ne vois pas de solution qui soit plus simple que le problème qu'elle résout. Donc je pense que je vais rajouter une ligne de code à chaque fois que je modifie l'objet manuellement. Merci pour vois contributions.
Lancer un signal à chaque modification d'un objet
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
Python c'est bon, mangez-en.
Python c'est bon, mangez-en.
Python c'est bon, mangez-en.
Python c'est bon, mangez-en.
Celui qui trouve sans chercher est celui qui a longtemps cherché sans trouver.(Bachelard)
La connaissance s'acquiert par l'expérience, tout le reste n'est que de l'information.(Einstein)
Python c'est bon, mangez-en.
Python c'est bon, mangez-en.
Celui qui trouve sans chercher est celui qui a longtemps cherché sans trouver.(Bachelard)
La connaissance s'acquiert par l'expérience, tout le reste n'est que de l'information.(Einstein)
Celui qui trouve sans chercher est celui qui a longtemps cherché sans trouver.(Bachelard)
La connaissance s'acquiert par l'expérience, tout le reste n'est que de l'information.(Einstein)