Seite 1 von 1
Änderungen in einem Dict mitbekommen...
Verfasst: Donnerstag 4. Januar 2007, 11:36
von jens
Ich habe eine Klasse, die von dict ableitet. Ich hätte gern innerhalb der Klasse gewusst, ob sich Daten geändert haben.
Ich dachte mit ich überschreibe einfach die __setitem__ Methode und setzte da einen Wert um... Aber so einfach ist das nicht:
Code: Alles auswählen
fake_db = {
1 : {"jo": "Daten für Modul 1"},
2 : {
"jup": "Daten für Modul 2",
"data": {},
},
}
class PluginConfig(dict):
_cache = {}
def __init__(self, module_id):
self.module_id = module_id
self.modified = False
def init2(self):
if self.module_id in self._cache:
# Daten sind schon im cache
print "Aus dem cache"
dict.__init__(self, self._cache[self.module_id])
return
# Daten sind noch nicht im cache -> aus db holen
print "aus der DB"
data = fake_db[self.module_id]
# In cache eintragen
self._cache[self.module_id] = data
dict.__init__(self, data)
def __setitem__(self, key, value):
print "setitem!"
self.modified = True
dict.__setitem__(self, key, value)
def commit(self):
print "commit!"
print "self.modified:", self.modified
print dict.__str__(self)
print self._cache[self.module_id]
p = PluginConfig(1)
p.init2()
print p
print "-"*79
p = PluginConfig(1)
p.init2()
print p
p["jo"] = "neu" # Hier geht es, natürlich
p.commit()
print "-"*79
p = PluginConfig(2)
p.init2()
print p
p["data"]["neu"] = 1 # Hier nicht
p.commit()
Wie man sehen kann, wird bei
p["data"]["neu"] = 1 nicht __setitem__ aufgerufen...
Wie könnte ich das Lösen?
EDIT: Eine Lösung die mir einfällt: Ich mache eine deepcopy und vergleichen dann...
Verfasst: Donnerstag 4. Januar 2007, 11:42
von jens
Jup, mit deepcopy geht's:
Code: Alles auswählen
import copy
fake_db = {
1 : {"jo": "Daten für Modul 1"},
2 : {
"jup": "Daten für Modul 2",
"data": {},
},
}
class PluginConfig(dict):
_cache = {}
def __init__(self, module_id):
self.module_id = module_id
def init2(self):
if self.module_id in self._cache:
# Daten sind schon im cache
print "Aus dem cache"
dict.__init__(self, self._cache[self.module_id])
return
# Daten sind noch nicht im cache -> aus db holen
print "aus der DB"
data = fake_db[self.module_id]
# In cache eintragen
self._cache[self.module_id] = copy.deepcopy(data)
dict.__init__(self, data)
def commit(self):
print "commit!"
if self._cache[self.module_id] != dict(self):
print "modified!"
else:
print "not modified"
print dict.__str__(self)
print self._cache[self.module_id]
p = PluginConfig(1)
p.init2()
print p
p.commit()
print "-"*79
p = PluginConfig(1)
p.init2()
print p
p["jo"] = "neu" # Hier geht es, natürlich
p.commit()
print "-"*79
p = PluginConfig(2)
p.init2()
print p
p["data"]["neu"] = 1 # Hier nicht
p.commit()
Kennt jemand noch eine andere Lösung???
Verfasst: Donnerstag 4. Januar 2007, 11:45
von cracki
p['data']['neu'] = irgendwas
dann wird nicht p.__setitem__ aufgerufen, sondern p.__getitem__('data').__setitem__('neu', irgendwas)
Verfasst: Donnerstag 4. Januar 2007, 11:48
von jens
Da hast du wohl recht... Aber das irgendwie "mit zu Protokollieren" ist wohl etwas schwierig...
Da die Daten eh als pickle in die DB wandern, kann der Programierer jede Form von picklebaren Daten ablegen...
Ich nehme erstmal die deepcopy Version...
EDIT: Warum mir deepcopy nicht gefällt: Ich hab es zwar nicht nachgemessen, aber ich kann mir vorstellen, das die deepcopy-Aktion einige Zeit kostet.
Allerdings dürfte das pickeln und zurück schreiben in die DB mehr Zeit kosten. Zumal das ändern der Daten viel weniger vorkommen sollte, als das lesen der Daten
Verfasst: Donnerstag 4. Januar 2007, 12:04
von CM
Hoi Jens,
ein paar Kommentare, vielleicht hilft es ja:
jens hat geschrieben:EDIT: Warum mir deepcopy nicht gefällt: Ich hab es zwar nicht nachgemessen, aber ich kann mir vorstellen, das die deepcopy-Aktion einige Zeit kostet.
Meiner Erfahrung nach: Ja, das tut es. Natürlich, das ist ja auch zu erwarten. Allerdings sind die Objekte, bei denen ich das spüre auch locker 2 bis 4 Mb groß. Einen Benchmark habe ich zwar nicht, aber selbst in dem geschilderten Fall, fällt es mit < 1 s pro Objekt (AMD 64 bit auf einem Acer Aspire 5020) nicht ins Gewicht.
jens hat geschrieben:
Allerdings dürfte das pickeln und zurück schreiben in die DB mehr Zeit kosten. Zumal das ändern der Daten viel weniger vorkommen sollte, als das lesen der Daten
Kommt natürlich drauf an. Aber kann Deine DB-Software (ich weiß leider nicht aus alten Threads, was Du nutzt) u. U. Änderungen verfolgen oder zumindest irgendwie anzeigen?
Besten Gruß im neuen Jahr,
Christian
Verfasst: Donnerstag 4. Januar 2007, 12:11
von jens
Ich nutzte MySQL... Aber warum soll mir das helfen Änderungen aufzuzeigen???
Also ich will vor dem updaten der DB Daten prüfen, ob das überhaupt notwendig ist.
Der einfachste weg wäre es wohl die Daten immer zu picklen und in die DB zu schreiben
Das dürfte IMHO mehr Performance kosten, als mit hilfe von deepcopy nachzuprüfen ob ein DB-Update überhaupt sein muß
Die Frage ist, ob es was schnelleres als die deepcopy-Lösung gibt...
Verfasst: Donnerstag 4. Januar 2007, 13:44
von rayo
Hi
Also wenn du nur Dictionaries/Listen hast, kannst du es so lösen:
Code: Alles auswählen
class WatchedDict(dict):
modified = False
def __setitem__(self, key, value):
if isinstance(value, dict):
value = WatchedDict(value)
WatchedDict.modified = True
dict.__setitem__(self, key,value)
def save(self):
if WatchedDict.modified:
#speichern
WatchedDict.modified = False
else:
print 'Dict wurde nicht veraendert'
So wird einfach jedes Dictionary in ein WatchedDict umgewandelt und lauscht somit auf jedem __setitem__.
Gruss
Verfasst: Donnerstag 4. Januar 2007, 13:56
von jens
Hm... Naja, ich könnte mir schon vorstellen es auf Dicts und Listen zu beschränken... Warum nicht...
Aber müsste ich nicht dann bei save() die Datenstruktur rekursiv durchsuchen, ob nicht irgendwo was geändert wurde???
Verfasst: Freitag 5. Januar 2007, 09:46
von rayo
Hi
Nein, wie du siehst verwende ich WatchedDict.modified, eine Klassenvariable (sprich die gibts nur einmal). Diese wird von jeder Instanz aus geändert.
Also ist WatchedDict.modified == True wenn irgendwo in der Struktur ein __setitem__ aufgerufen wurde.
Gruss
Verfasst: Freitag 5. Januar 2007, 10:17
von jens
Ne, so einfach ist das nicht... Ich hab mal einen Test gemacht:
Code: Alles auswählen
class WatchedDict(dict):
modified = False
def __setitem__(self, key, value):
if isinstance(value, dict):
value = WatchedDict(value)
WatchedDict.modified = True
dict.__setitem__(self, key,value)
def save(self):
if WatchedDict.modified:
#speichern
WatchedDict.modified = False
print 'Dict wurde veraendert'
else:
print 'Dict wurde nicht veraendert'
init_data = {"dict": {"liste":[1,2,3], "test1": 1}, "test2": "jup"}
d = WatchedDict(init_data)
print d
print "-"*79
d["dict"]["liste"][1] = 9
print d
d.save() # unbemerkt
print
d["dict"]["test1"] = "yes"
print d
d.save() # unbemerkt
print
d["test"] = [1,2]
print d
d.save() # OK
print
Ausgabe:
Code: Alles auswählen
{'test2': 'jup', 'dict': {'liste': [1, 2, 3], 'test1': 1}}
-------------------------------------------------------------------------------
{'test2': 'jup', 'dict': {'liste': [1, 9, 3], 'test1': 1}}
Dict wurde nicht veraendert
{'test2': 'jup', 'dict': {'liste': [1, 9, 3], 'test1': 'yes'}}
Dict wurde nicht veraendert
{'test': [1, 2], 'test2': 'jup', 'dict': {'liste': [1, 9, 3], 'test1': 'yes'}}
Dict wurde veraendert
Ist auch klar, warum es so nicht geht... Das dict innerhalb der init_data ist halt ein normales dict und kein WatchedDict...
Ich müsste also die __init__ Methode vom WatchedDict überschreiben und dann die Datenstruktur rekursiv durchgehen und alle normalen dicts in ein WatchedDict verwandeln. Das gleiche müsste ich zumindest auch noch für Listen machen...
Wenn ich das so mache, bin ich fast schon bei der deepcopy Variante. Wobei in dabei eine Kopie aller Daten existiert und bei der WatchedDict keine doppelten Daten im Speicher sind.
Verfasst: Freitag 5. Januar 2007, 19:32
von Luzandro
Nachdem mich das jetzt auch interessiert hat, hab ich einen allgemeineren Wrapper geschrieben, der prinzipiell funktionieren sollte - ob es wirklich so sinnvoll ist, weiß ich nicht
Grundsätzlich ist die Idee, dass bei jedem getItem/Attribute das erhaltene Objekt wieder durch ein ObserverObjekt ersetzt wird, welches eine Referenz auf das erste hat und bei jedem setItem/Attribute wird der modified-Status von diesem Root-Objekt auf True gesetzt
Was allerdings natürlich nicht erkannt wird ist, wenn ein enthaltenes Objekt direkt geändert wird ohne Aufruf der get-Methoden
Code: Alles auswählen
class ObservedObject(object):
def __init__(self, obj, rootObject=None):
self._obj = obj
if rootObject is None:
self._root = self
else:
self._root = rootObject
self.modified = False
def __setattr__(self, name, value):
if name.startswith('_') or name == 'modified':
object.__setattr__(self, name, value)
else:
object.__setattr__(self._obj, name, value)
self._root.modified = True
def __getattribute__(self, name):
if name.startswith('_') or name == 'modified':
return object.__getattribute__(self, name)
else:
value = object.__getattribute__(self._obj, name)
if not isinstance(value, ObservedObject):
value = ObservedObject(value, rootObject=self._root)
object.__setattr__(self._obj, name, value)
return value
def __getitem__(self, key):
value = self._obj[key]
if not isinstance(value, ObservedObject):
value = ObservedObject(value, rootObject=self._root)
self._obj[key] = value
return value
def __setitem__(self, key, value):
self._obj[key] = value
self._root.modified = True
def __str__(self):
return str(self._obj)
def __repr__(self):
return repr(self._obj)
init_data = {"dict": {"liste":[1,2,3], "test1": 1}, "test2": "jup"}
d = ObservedObject(init_data)
print d
print "-"*79
d["dict"]["liste"][1] = 9
print d
print "modified? ", d.modified
d.modified = False
print
d["dict"]["test1"] = "yes"
print d
print "modified? ", d.modified
d.modified = False
print
d["test"] = [1,2]
print d
print "modified? ", d.modified
d.modified = False
print
e = {'foo': 'bar'}
d['narf'] = e
print d
print "modified? ", d.modified
d.modified = False
print
e['foo'] = 'hurz'
print d
print "modified? ", d.modified #falsch
d.modified = False
print