GUI für einen Daemon - Vorsicht Anfänger!

Programmierung für GNOME und GTK+, GUI-Erstellung mit Glade.
Antworten
docmcfly
User
Beiträge: 5
Registriert: Donnerstag 23. Februar 2012, 08:29

Hallo zusammen,

Ich hab mir ein kleinen Daemon geschrieben. Auf der Konsole fünktioniert der ganz gut. Da der Dienst ein kleiner lokaler und temporär begerenzter Hintergrund-Job sein soll, dachte ich mir, ich bastel eine GUI dazu.

Kurzer Hintergrund: Der Daemon überwacht ein angebenes Verzeichnis und legt Sicherheitskopien aller Dateien an, sobald diese neu gespeichert werden. Das brauche ich wenn ich bspw. mit Inkscape arbeite. So hab ich mit jedem Speichern eine neue Version der Datei - und kann zu alten Versionen immer wieder zurück.
Also bevor ich Inscape starte, gebe ich mein Projekt-Ordner an und starte den Daemon. So viel zur Theorie.

Als Start in das Projekt habe ich das quickly-Tool von Ubunutu verwendet. Leider funktioniert der Start des Daemon nicht richtig. Immer wieder bleibt die GUI hängen, die Logausgaben sind alle doppelt und andere Kleinigkeiten. Meine Vermutung: Via Button-Action einen anderen Thread-starten ist nicht so trivial.

Unter http://clemens.cylancer.net/local-history.zip hab ich mal das Projekt zum Download bereitgestellt.

Da ich normalerweise in der Java-Welt unterwegs bin, bin ich mit den Namenskoventionen nicht so vertraut. Wer da Kritik an dem Code üben möchte (besonders die ServiceEntity.py) - nur zu : bin offen, was das angeht.

Danke für hilfreiche Antworten
Gruß Clemens

PS: Python programmiere ich erst seit ca. 6 Tagen.
BlackJack

@docmcfly: Fangen wir mal mit dem Unwichtigsten an: Namenskonventionen. :-) Da gibt's
PEP 8 -- Style Guide for Python Code. Und `ServiceEntity` kling auch sehr nach Java. Da wird ja dazu geneigt eigentlich überflüssiges an den Namen anzuhängen wie `Entity`, `Manager`, und so weiter. Einfach nur `Service` würde es doch auch tun. In Java ist das ja meistens weil man noch ein paar andere Klassen oder Interfaces drumherum hat, die sonst alle gleich heissen müssten. Also zum Beispiel das man ein `Service`-Interface hat und die konkrete Klasse dann halt anders nennen *muss*.

Ich bin mir nicht sicher ob Du bei Python Klassenattribute verstanden hast. Das was auf Ebene des Klassenkörpers an Namen gebunden wird, wäre in Java ``static``. Und Du nutzt den Umstand für Defaultwerte wo im Laufe der Zeit dann (eventuell) zusätzlich noch Attribute mit dem gleichen Namen auf dem Exemplar eingeführt werden. Du hast also diese Namen doppelt und je nach dem ob er auf dem Exemplar existiert oder nicht, wird beim Lesezugriff auf das Klassenattribut oder das Exemplar-Attribut zugegriffen. Das kann in einigen wenigen Fällen sinnvoll sein, ist aber etwas unsauber. In der Regel würde man auf Klassen nur Konstanten ablegen und die auch entsprechend benennen. Wo es zum Beispiel absolut keinen Sinn macht ist `ServiceEntity._directory`. Das Attribut hast Du einmal auf der Klasse und dann noch einmal auf jedem Exemplar. Das auf der Klasse ist *immer* an `None` gebunden und wird *nie* benutzt. Das kann man sich also auch sparen.

`os.path` muss man nicht importieren, dass `path`-Attribut wird schon durch das importieren von `os` an das Modulobjekt gebunden.

Es ist zwar sowieso nicht so ganz plattformunabhängig, aber wenn man schon `os.path.join()` verwendet, dann sollte man bei den Argumenten nicht schon plattformspezifische Pfadtrenner drin haben. ;-)

Namen sollten nicht zu kurz und zu kryptisch sein. Man sollte keine Abkürzungen verwenden, solange die nicht allgemein bekannt sind. `_l` ist zum Beispiel ein ganz schlechter Name. `log` oder `_log` ist nun nicht so viel länger, aber man weiss was das ist, wenn man es liest.

Die `ServiceEntity.__init__()` macht auf mich ein wenig den Eindruck als wenn dort zwei Methoden in eine geschrieben wurden. Das würde ich trennen, oder das `options`-Argument durch mehrere Argumente ersetzen und dafür dann ein wenig von dem Code aus der `__init__()` zum Aufrufer verlagern. Das kann man ja so ein bisschen als UI-Code ansehen, der in die Programmlogik gerutscht ist.

`set_directory()` wird in der `__init__()` inkonsistent verwendet und nirgends scheint der Rückgabewert verwendet zu werden!?

`os.path.exists()` ist eine Funktion die man sich in sehr vielen Fällen sparen kann. Wenn man danach die Datei öffnet, kann es trotzdem zu einer Ausnahme kommen, weil zwischen den beiden Funktionen die Datei gelöscht worden sein kann, also muss man wenn es robust sein soll, sowieso Ausnahmebehandlung beim `open()` machen. Da wäre dann der Fall das die Datei nicht existiert auch mit abgedeckt.

Dateien sollte man mit der ``with``-Anweisung öffnen um sicher zu stellen, dass sie auch in jedem Fall wieder geschlossen wird.

In `fobj` ist das `obj` etwas redundant, denn es ist ein Name und in Python ist *alles* was man an einen Namen binden kann, ein Objekt. `obj` ist also keine Zusatzinformation für den Leser.

Bei `read_pid()` finde ich es etwas inkonsistent, dass eine PID-Datei mit falschem Inhalt eine Ausnahme auslöst, eine mit richtigem Inhalt wo es den Prozess aber nicht (mehr) gibt, nicht von *keiner* PID-Datei unterschieden werden kann.

Statt ``not x is y`` sollte man ``x is not y`` schreiben. Das ist nur ein Operator und es ist lesbarer. Bei ``if not self._notifier is None or not pid is None:`` musste ich zumindest mehr nachdenken als mir lieb war. :-) (An der Stelle könnte man eventuell auch ausnutzen was Python in diesem Kontext als wahr und falsch ansieht und einfach ``if self._notifier and pid:`` schreiben, unter der Annahme, dass es keine PID mit dem Wert 0 geben darf.

Der Name `msg` wird dort übrigens an einen Wert gebunden, dann aber nicht verwendet.

In der Programmlogik sollte man niemals `exit()` aufrufen. Damit kann man diesen Code nicht einfach wiederverwenden. Vielleicht möchte man ja eine Aufrufebene höher gerne etwas weniger drastisch auf bestimmte Bedingungen reagieren als den *gesamten* Prozess zu beenden. Stell Dir mal vor Du willst das Programm auf mehrere unabhängig überwachte Verzeichnisse erweitern und dazu werden dann mehrere `Service`-Objekte erstellt. Und wenn es bei einem davon Probleme beim Starten gibt, wird gleich alles beendet, obwohl andere problemlos gestartet sind und arbeiten.

Die `make_dir()` ist viel zu aufwändig und Pfad oder `None` sind IMHO die falschen Rückgabewerte. Es sollte der Pfad zurückgegeben werden oder eine Ausnahme ausgelöst werden, wenn es den nicht gibt. Spezielle Fehlerwerte sind ein Relikt aus Sprachen, bei denen es keine Ausnahmen gibt. Genau so etwas wollte man mit Ausnahmen los werden.

`EventHandler.__init__()` ruft nicht die `__init__()` der Basisklasse auf.

`make_dir()`, `archive_archive()`, und `create_new_filename()` müssten nicht zwingend Methoden sein. Nur so als Anmerkung für jemanden der von Java kommt.

In der `__init__.py` erstellst Du ein `ServiceEntity`-Exemplar, dass nirgends verwendet wird!?

Und `LocalHistoryWindow` ist syntaktisch nicht korrekt. Ein Versuch das Ganze zu starten endet in einem `IndentationError`:

Code: Alles auswählen

$ python ./bin/local-history 
Traceback (most recent call last):
  File "./bin/local-history", line 32, in <module>
    import local_history
  File "/home/marc/tmp/local-history/local_history/__init__.py", line 14, in <module>
    from local_history import LocalHistoryWindow
  File "/home/marc/tmp/local-history/local_history/LocalHistoryWindow.py", line 30
    def finish_initializing(self, builder): # pylint: disable=E1002
      ^
IndentationError: expected an indented block
docmcfly
User
Beiträge: 5
Registriert: Donnerstag 23. Februar 2012, 08:29

Danke BlackJack... Ich werde die Tage anfangen, mal alle Deine Vorschläge einzuarbeiten.

Noch so ein paar Fragen:

Wenn ich Dich richtig verstehe, gibt es keine zentrale Stelle in einer Python-Klasse wo ich sehe, welche Member-Variablen existieren? Muss man alle Methoden durchschauen?

Bspw. soll ja das Verzeichnis (self._directory) allen Methoden zu verfügung stehen.

kann ich zwei "ServiceEntity.__init__()"-Methoden implementieren mit unterschiedlichen Signaturen? Einmal für den Konsolenmodus und einmal wenn man den Service irgendwo einbinden will.

Melde mich die tage...fürs erste erst mal Danke...
Gruß Clemens
BlackJack

@docmcfly: Es gibt technisch gesehen keine zentrale Stelle an der Attribute festgelegt werden müssen. Es reicht nicht einmal alle Methoden durch zu gehen, denn auch von aussen kann man zur Laufzeit neue Attribute setzen [1]_. Und an der Stelle sollte man aus Java-Sicht vielleicht noch im Hinterkopf behalten, dass Methoden auch Attribute sind — der ``.``-Operator unterscheidet da nicht zwischen. Dem ist egal ob der Name rechts vom Operator beispielsweise an eine ganze Zahl oder eine Methode gebunden ist. Die „Magie” bei Methoden ist einfach nur, dass es Attribute sind, deren Objekte bestimmte Methoden implementieren, die dafür sorgen, dass bei einem Attributzugriff ein aufrufbares Objekt zurückgegeben wird, das beim Aufruf das Objekt von dem es abgerufen wurde, als erstes Argument an die Methode übergibt, und die anderen Argumente „durch reicht”.

Es ist aber guter Stil, dass innerhalb der `__init__()` oder zumindest nach deren Abarbeitung alle Attribute des Objekts an einen Wert gebunden wurden.

Man kann immer nur eine Methode mit dem gleichen Namen definieren. Unterscheiden könnten sich Signaturen dann ja sowieso nur in der Länge der Argumentliste und den Argumentnamen. Um beim Aufruf zwischen beiden zu unterscheiden, müsste man dann also auf die Anzahl der Argumente setzen oder die Argumentnamen explizit beim Aufruf angeben. Das würde den Aufruf deutlich komplizierter gestalten.

Für alternative „Konstruktoren” verwendet man in Python üblicherweise `classmethod()`\s.

.. [1] Das gilt nicht für wirklich für jedes Objekt, da man bei Typen die in C implementiert sind, zum Beispiel verhindern kann, dass man dynamisch Attribute hinzufügen kann.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Kurzer Einwand: als ich die Anforderung gesehen habe, dachte ich mir "klar, git + inotify". Stellt sich raus, sowas gibt es natürlich schon.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
docmcfly
User
Beiträge: 5
Registriert: Donnerstag 23. Februar 2012, 08:29

Das ist jetzt aber deprimierend... hat einfach jemand anderes das schon programmiert. :( und das auch noch mit dem potenten Version-Syztem git verbunden.

Ich mach die Änderungen noch fertig... dann poste ich das und vergesse Python ganz schnell wieder. ;)

Gruß Clemens

PS: ein Ruby-Projekt wartet ja auch noch auf mich.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

docmcfly hat geschrieben:Das ist jetzt aber deprimierend... hat einfach jemand anderes das schon programmiert. :( und das auch noch mit dem potenten Version-Syztem git verbunden.
Kannst ja sowas in Python machen. Finds eh am besten das mit nem Versionskontrollsystem zu verbinden, statt da was selbstgestricktes zu haben. Oder vielleicht gibt es auch das in Python schon, da kannste ja mitarbeiten.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
docmcfly
User
Beiträge: 5
Registriert: Donnerstag 23. Februar 2012, 08:29

Hallo Leonidas,

Mal sehen ob ich diese git LIVE zum laufen bekomme. hab von git keine Ahnung.

Das sollte ja der Vorteil von meinem Tool-chen sein. Man startet es kurz bevor man anfängt zu arbeiten ... und am ende macht man es wieder aus.
Ohne große Installation und Konfiguration ... CSV und SVN hab ich schon oft eingerichtet und das ist glaube ich nix, wenn man mal ein paar Versionen seiner vorherigen Zeichnungen haben möchte.

Jetzt muss ich mich noch mit git rum schlagen ;)

Es einmal danke...

Gruß Clemens
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

docmcfly hat geschrieben:Ohne große Installation und Konfiguration ... CSV und SVN hab ich schon oft eingerichtet und das ist glaube ich nix, wenn man mal ein paar Versionen seiner vorherigen Zeichnungen haben möchte.
Dann wird dich Git und insgesamt verteilte Versionskontrolle (wie bzr, was du offenbar auch zu nutzen scheinst) positiv überraschen. Denn wenn ich Versionen von Textdateien haben will, ist nichts* einfacher einzusetzen als DVCS.

*gut, automatische Filesystem-Snapshots, aber das ist momentan kaum verbreitet.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
docmcfly
User
Beiträge: 5
Registriert: Donnerstag 23. Februar 2012, 08:29

Hallo Leonidas,

Hab's ausprobiert und für doof befunden...

Hab ein neues Verzeichnis erzeugt - "git init" ausgeführt. Das GitLive-Skript rein kopiert und gestartet.

Code: Alles auswählen

** (seed:4812): CRITICAL **: Line 25 in gitlive.js: TypeError 'undefined' is not an object (evaluating 'imports.StatusIcon.StatusIcon')
und Hallo BlackJack,

Hab ein paar Änderungen an dem Skript gemacht und jetzt kann man es starten...
http://clemens.cylancer.net/local-history.zip

Was nicht funktioniert ist folgendes:

Drücke den toggle-Button zum starten meines Dienstes... Der Dienst startet... Auf der Konsole sieht es so aus, als wäre die Anwendung beendet, ABER die GUI ist noch da und reagiert nicht mehr. Ich kann Sie nur noch mit "kill" abschießen.

Wie kann man das beheben?

Danke im Voraus für hilfreiche Kommentare...
Gruß Clemens
Antworten