Seite 1 von 1

Etwas tun, wenn ein Attribut verändert wird

Verfasst: Sonntag 11. Januar 2009, 15:48
von snafu
Hallo!

Mein Anliegen ist diesmal etwas komplizierter. Ich denke aber, es ist sinnvoll, hier ein bißchen weiter auszuholen.

In einem Skript frage ich per DBus die Login-Events meiner Pidgin-Buddies ab. Darum kümmert sich die Klasse "LoginListener". Sie fristet ihr Dasein in einer Endlosschleife und gibt bei jedem Login eines Buddies mittels Callback-Funktion seinen Alias und die aktuelle Uhrzeit zurück.

Das klappt bisher alles ganz gut. Nun möchte ich aber die jeweils letzte Login-Uhrzeit meiner Buddies abspeichern. In etwa so:

Code: Alles auswählen

logtimes = { 'snafu': '11.01.2009 - 15:29', 'xyz': '10.01.2009 - 14:30', usw }
Die Callback-Funktion macht deshalb bei ihrem Aufruf mit dem Attribut meiner Klasse "Logger" folgendes:

Code: Alles auswählen

logger.logtimes[alias] = logtime
(der Code folgt am Ende, das mag im Moment etwas verwirrend klingen)

Hintergrund des Vorhabens ist, dass ich gerne wissen möchte, inwiefern die Leute meine Nachrichten gelesen haben könnten (sprich: zwischenzeitlich online waren). Wenn nämlich mal jemand "nachweislich" ein paar Tage nicht online war, muss ich mich wenigstens nicht wundern, warum derjenige nicht antwortet. Und da ich Pidgin hier fast den ganzen Tag am Laufen habe (also ständig verbunden bin), kann ich das IMHO einigermaßen nachhalten.

Zurück zum Thema: Geplant ist nun, dass Pickle mir mein Dictionary nach jeder Änderung der Werte in eine Datei schreibt. Die jeweils alten Werte werden dabei überschrieben, denn mich interessiert ja nur der letzte Login. Das soll vermeiden, dass bei einem Systemabsturz oder etwas ähnlichem, die aktuellen Daten verloren sein könnten. Außerdem könnte somit der Logger als Hintergrundprozess in seinem Loop laufen, aber gleichzeitig kann ein neuer Prozess mittels "last_login(buddy)" prüfen, wann snafu zuletzt online war... ;)

Meine Frage ist nun: Bietet Python eine einfache Möglichkeit, die Veränderungen an einem Attribut zu überwachen, damit ich entsprechend meine Save-Funktion aufrufen kann? Ich dachte an sowas:

Code: Alles auswählen

while True:
    if self.logtimes changed:
        self.save_logtimes()
Achso, hier der Code: http://paste.pocoo.org/show/99079/

Verfasst: Sonntag 11. Januar 2009, 15:56
von DatenMetzgerX
Hier würde sich das Observerpattern eignen.
Deine Klasse LoginListeners fürt eine Liste mit allen registrierten Listeners (auf Klassenebene). Im get_login_event iterierst du dann über die Liste und rufst z.b. auf allen Instanzen user_logged_in(user) auf.

Somit können soviele Klassen wie du möchtest über diesen event informiert werden. Dadurch wäre das Modul leicht erweiterbar.

Verfasst: Sonntag 11. Januar 2009, 19:37
von Dauerbaustelle
__setattr__ überschreiben...

Verfasst: Sonntag 11. Januar 2009, 19:44
von Birne94
schreb einfach ne eigene Klasse:

Code: Alles auswählen

class MyDic(dict):
    def __init__(self, parent, *args, **kw):
        dict.__init__(self, *args, **kw)
        self._parent = parent
    def __setitem__(self, item, value):
        dict.__setitem__(self, item, value)
        self._parent.save_logtimes()
Das kannst du dann wie ein normales dict verwenden:

Code: Alles auswählen

self._logtimes = MyDic(self)
self._logtimes["foo"] = "bla"
print (self._logtimes)

Verfasst: Sonntag 11. Januar 2009, 19:52
von Dauerbaustelle

Code: Alles auswählen

In [1]: class Foo(object):
    bar = False
    bar_changed = False
    def __setattr__(self, name, value):
        if name == 'bar' and value != self.bar:
            self.bar_changed = True
        self.__dict__[name] = value
In[2]: f = Foo()
In [3]: f.bar_changed
Out[3]: False

In [4]: f.bar = True

In [5]: f.bar_changed
Out[5]: True

Verfasst: Sonntag 11. Januar 2009, 19:57
von rayo
Hi

Hier nochmals eine Lösung:

Code: Alles auswählen

import pickle
import os

class PersistentDict(dict):
    def __init__(self, filename, *args, **kwargs):
        dict.__init__(self, *args, **kwargs)
        self.filename = filename
        if os.path.exists(filename):
            self.load(self.filename)
    
    def __setitem__(self, key, value):
        dict.__setitem__(self, key, value)
        try:
            self.save(self.filename)
        except AttributeError:
            pass
    
    def save(self, filename):
        f = open(filename, 'wb')
        pickle.dump(self, f, pickle.HIGHEST_PROTOCOL)
        f.close()
    
    def load(self, filename):
        f = open(filename, 'rb')
        self.update(pickle.load(f))
        f.close()
Da muss man beim erstellen nur noch die Datei angeben und das Dictionary lädt sich automatisch wenn die Datei bereits existiert und speichert sich bei jeder Änderung.

Gruss

Verfasst: Sonntag 11. Januar 2009, 21:17
von snafu
Genial. Es funktioniert. Vielen Dank euch allen. :)

http://paste.pocoo.org/show/99147/

Verfasst: Dienstag 13. Januar 2009, 16:44
von snafu
Jetzt ist doch noch ein Problem aufgekommen. Ich habe das Programm etwas erweitert und möchte es auch bald der Allgemeinheit zur Verfügung stellen. Komischerweise erhalte ich jetzt eine Fehlermeldung, obwohl die die dict-Klasse so gelassen habe wie in meinem letzten Link. Kann mir da jemand weiterhelfen?

Code: Alles auswählen

~$ logtimes test
Traceback (most recent call last):
  File "/home/sebastian/logtimes.py", line 151, in <module>
    main()
  File "/home/sebastian/logtimes.py", line 147, in main
    lastlog_from_cmdline(args)
  File "/home/sebastian/logtimes.py", line 113, in lastlog_from_cmdline
    lastlog = last_login(buddy, LOGFILE)
  File "/home/sebastian/logtimes.py", line 98, in last_login
    return logtimes[buddy]
TypeError: list objects are unhashable
Der zugehörige Code: http://paste.pocoo.org/show/99450/

EDIT: Der logging-Import ist natürlich im Moment überflüssig. Hatte ich vergessen, rauszunehmen.

Verfasst: Dienstag 13. Januar 2009, 17:02
von helduel
Moin,

Code: Alles auswählen

def lastlog_from_cmdline(*args):
vs.

Code: Alles auswählen

    if args:
        lastlog_from_cmdline(args)
Entweder du lässt das * oben weg, oder du fügst es unten hinzu. Sonst ist buddy in Zeile 98 eine Liste. Da Listen nicht hashable sind, können sie auch nicht als Key verwendet werden.

Gruß,
Manuel

Verfasst: Dienstag 13. Januar 2009, 17:37
von snafu
Boah, ich hab mir voll den Ast abgesucht. Vielen Dank. Läuft jetzt. :)

http://paste.pocoo.org/show/99462/