Änderungen in einem Dict mitbekommen...

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Benutzeravatar
jens
Moderator
Beiträge: 8458
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Änderungen in einem Dict mitbekommen...

Beitragvon jens » Donnerstag 4. Januar 2007, 11:36

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... :?

CMS in Python: http://www.pylucid.org
GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
jens
Moderator
Beiträge: 8458
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Beitragvon jens » Donnerstag 4. Januar 2007, 11:42

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???

CMS in Python: http://www.pylucid.org
GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
cracki
User
Beiträge: 72
Registriert: Montag 25. Dezember 2006, 05:01

Beitragvon cracki » Donnerstag 4. Januar 2007, 11:45

p['data']['neu'] = irgendwas

dann wird nicht p.__setitem__ aufgerufen, sondern p.__getitem__('data').__setitem__('neu', irgendwas)
...meh...
Benutzeravatar
jens
Moderator
Beiträge: 8458
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Beitragvon jens » Donnerstag 4. Januar 2007, 11:48

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 ;)

CMS in Python: http://www.pylucid.org
GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
CM
User
Beiträge: 2464
Registriert: Sonntag 29. August 2004, 19:47
Kontaktdaten:

Beitragvon CM » Donnerstag 4. Januar 2007, 12:04

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
Benutzeravatar
jens
Moderator
Beiträge: 8458
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Beitragvon jens » Donnerstag 4. Januar 2007, 12:11

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...

CMS in Python: http://www.pylucid.org
GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
rayo
User
Beiträge: 773
Registriert: Mittwoch 5. November 2003, 18:06
Wohnort: Schweiz
Kontaktdaten:

Beitragvon rayo » Donnerstag 4. Januar 2007, 13:44

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
Benutzeravatar
jens
Moderator
Beiträge: 8458
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Beitragvon jens » Donnerstag 4. Januar 2007, 13:56

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???

CMS in Python: http://www.pylucid.org
GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
rayo
User
Beiträge: 773
Registriert: Mittwoch 5. November 2003, 18:06
Wohnort: Schweiz
Kontaktdaten:

Beitragvon rayo » Freitag 5. Januar 2007, 09:46

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
Benutzeravatar
jens
Moderator
Beiträge: 8458
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Beitragvon jens » Freitag 5. Januar 2007, 10:17

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=]{'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[/code]

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.

CMS in Python: http://www.pylucid.org
GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
Luzandro
User
Beiträge: 87
Registriert: Freitag 21. April 2006, 17:03

Beitragvon Luzandro » Freitag 5. Januar 2007, 19:32

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

Wer ist online?

Mitglieder in diesem Forum: Bing [Bot]