Etwas tun, wenn ein Attribut verändert wird

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.
Antworten
Benutzeravatar
snafu
User
Beiträge: 5461
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Sonntag 11. Januar 2009, 15:48

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/
shcol (Repo | Doc | PyPi)
Benutzeravatar
DatenMetzgerX
User
Beiträge: 398
Registriert: Freitag 28. April 2006, 06:28
Wohnort: Zürich Seebach (CH)

Sonntag 11. Januar 2009, 15:56

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.
Dauerbaustelle
User
Beiträge: 996
Registriert: Mittwoch 9. Januar 2008, 13:48

Sonntag 11. Januar 2009, 19:37

__setattr__ überschreiben...
Birne94
User
Beiträge: 90
Registriert: Freitag 28. November 2008, 15:18
Kontaktdaten:

Sonntag 11. Januar 2009, 19:44

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)
Dauerbaustelle
User
Beiträge: 996
Registriert: Mittwoch 9. Januar 2008, 13:48

Sonntag 11. Januar 2009, 19:52

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
rayo
User
Beiträge: 773
Registriert: Mittwoch 5. November 2003, 18:06
Wohnort: Schweiz
Kontaktdaten:

Sonntag 11. Januar 2009, 19:57

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
Benutzeravatar
snafu
User
Beiträge: 5461
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Sonntag 11. Januar 2009, 21:17

Genial. Es funktioniert. Vielen Dank euch allen. :)

http://paste.pocoo.org/show/99147/
shcol (Repo | Doc | PyPi)
Benutzeravatar
snafu
User
Beiträge: 5461
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Dienstag 13. Januar 2009, 16:44

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.
shcol (Repo | Doc | PyPi)
Benutzeravatar
helduel
User
Beiträge: 300
Registriert: Montag 23. Juli 2007, 14:05
Wohnort: Laupheim

Dienstag 13. Januar 2009, 17:02

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
Benutzeravatar
snafu
User
Beiträge: 5461
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Dienstag 13. Januar 2009, 17:37

Boah, ich hab mir voll den Ast abgesucht. Vielen Dank. Läuft jetzt. :)

http://paste.pocoo.org/show/99462/
shcol (Repo | Doc | PyPi)
Antworten