pymp

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Antworten
Benutzeravatar
Matflasch
User
Beiträge: 41
Registriert: Donnerstag 25. März 2004, 15:42
Wohnort: Hamburg
Kontaktdaten:

Hallo,

ich habe in der letzten Zeit einen MP3 Player in Python geschrieben (befindet sich noch lange in der Entwicklung...). Ich hoffe, hier dürfen auch noch nicht fertig entwickelte Projekte vorgestellt werden... Der Player ist nun gerade soweit lauffähig, dass er benutzbar wird ;)
Und das ist der Punkt, an dem ich gern Kritik (positive, wie auch negative) ernten möchte (ich möchte mich, da ich nun gerade eine, wie ich finde, gute Basis des ganzen habe, nicht verrennen, nur weil man den Wald vor lauter Bäumen nicht sieht ;)

Benötigt wird:
>= Python 2.6
>= wxwidgets (2.8-gtk2-unicode)
>= python-gst0.10
(ein LyricWiki-Script, pylast (last.fm), wie auch mutagen sind externe Dateien, die ich aber mit in das Repository gepackt habe.)

Mit wxWidgets bin ich mitlerweile super zufrieden, jedoch stört mich der gstreamer ein wenig. Den möchte ich noch gerne ersetzen, so dass der Player plattformunabhängig werden kann, da der gstreamer sich mit Windows nicht gerade gut verträgt. Ich habe da auch schon an pyglet gedacht, aber bei der wxwidgets-mainloop und der pyglet-mainloop, das muss ich mir noch mal genauer anschauen ;)

Sofern die benötigten libs installiert sind, kann das Programm direkt gestartet werden aus dem src-package (pympapp.py).

Zum Code:
https://bitbucket.org/matflasch/pymp/

Soweit erstmal. Es geht auf jeden Fall stetig voran und danke im voraus! :)

gruß, matflasch
lunar

Qt und Phonon wäre naheliegende Alternativen zu wxWidgets und gstreamer. Den Quelltext selbst habe ich nicht näher angesehen, da ich weder wx noch gstreamer sonderlich gut kenne, und zumindest im Falle von wx auch nicht kennen will. Daher nur ein paar allgemeine Anmerkungen.

Die Abhängigkeiten mitzuliefern, ist zwar bequem, aber unschön. Besser ist es, mit distutils/setuptools ein vernünftiges Installationsskript zu schreiben, und die Abhängigkeiten dort aufzuführen. Dann kann man das Programm einfach mit "python setup.py install" bzw. besser mit "pip install ." installieren, wobei die Abhängigkeiten automatisch mitinstalliert werden.

In allen Dateien ist die Kodierungsangabe falsch, statt "utf8" sollte es "utf-8" heißen.

"pympapp.py" habe ich mir aus genannten Gründen nur oberflächlich angesehen, aber trotzdem ist mir einiges aufgefallen. Die Einrückung ist schlicht kaputt, sie mischt Tabulatoren und Leerzeichen, es ist aller Wahrscheinlichkeit nur Zufall, dass das Programm tatsächlich wie gewünscht funktioniert. Verwende außerdem keine keine "*"-Importe, sie müllen den Namensraum zu und führen folglich früher oder später zu Konflikte und in der Folge zu kryptischen und schwer zu findenden Fehlern, und keine magischen Zahlen wie in "PympApp._onKeyUp()":

Code: Alles auswählen

# Pfeil oben
    if keycode == 315:
        self._ctrls['lctPlaylist'].item_up()
        return
Zum einen ist das schwer lesbar, zum anderen umständlich, und wahrscheinlich nicht einmal portabel. So wäre es besser, der Kommentar ist dann auch überflüssig:

Code: Alles auswählen

if keycode == KEY_UP:
    # ...
"KEY_UP" sollte idealerweise irgendwo aus wx kommen, da es eigentlich Aufgabe der Bibliothek ist, Konstante für solche "magischen" Werte zur Verfügung zu stellen. Ansonsten musst Du sie eben selbst definieren.

Auch würde dieser Datei dringend mehr Struktur und Ordnung gut tun. Viele Stellen sind komplex, dabei aber vollkommen undokumentiert, unkommentiert und voller kryptischer Namen, e.g. "PympApp._build_tree()" oder "PympApp.__valid_entry()". "__valid_entry()" ist ein gutes Beispiel dafür, wie man nicht programmieren sollte:

Code: Alles auswählen

mand = True if 'and' in pattern else False
# […]
br = []
for p in pattern:
    p = p.strip()
    b0 = b1 = b2 = b3 = False
    if item[-1]: # path
        if p in item[-1].lower():
            b0 = True
    if item[1]: # title
        if p in item[1].lower():
            b1 = True
    if item[2]: # artist
        if p in item[2].lower():
            b2 = True
    if item[3]: # album
        if p in item[3].lower():
            b3 = True
    br.append(any([b0, b1, b2, b3]))
Jede Wette, dass Du in zwei Wochen selber nicht mehr weißt, was "b1" bis "b3" bedeuten, was in "item[3]" steht, und was die gesamte Methode überhaupt tut. Die erste Zeile ist übrigens einfach "mand = 'and' in pattern", der bedingte Ausdruck ist mithin überflüssig. Du wirst verstehen, dass ich besseres zu tun hatte, als versuchen, diesen Quelltext zu nachzuvollziehen. Ähnliche strukturelle Probleme hat der Großteil des Quelltext, auch in den anderen Modulen, ich spare mir jetzt die einzelne Auflistung aller Punkte, die mir aufgefallen sind.

Es ist reichlich überflüssig, "__version__", "__author__" und andere Metadaten in jeder Datei zu definieren, üblicherweise schreibt man das nur ein einziges Mal in die "__init__.py". Hast Du Dir dabei irgendetwas besonderes gedacht?!

In "collection.py" finden sich oft Konstrukte wie "except TypeError: pass". Was soll den das?! Typfehler sind normalerweise ein Zeichen für gravierende Fehler in einem Programm, die etwas mehr Aufmerksamkeit verdienen als sie einfach zu ignorieren! Die Ausnahmebehandlung ist hier insgesamt verbesserungsbedürftig, viel zu oft werden alle Ausnahmen mit "except Exception:" abgefangen und dann einfach mehr oder weniger ignoriert. Wenn schon, dann gibt in einem solchen Fall doch wenigstens den Traceback aus, sonst wird die Fehlersuche zur Lotterie.

Was Du Dir bei all den Konstanten in "config.py" gedacht hast, kann ich ebenfalls nicht nachvollziehen. Außerdem sollte der bloße Import eines Moduls keine Seiteneffekte wie beispielsweise das Erzeugen von Verzeichnissen haben, denn so kann man config.py weder testen, noch vernünftig im Interpreter ausprobieren.

In "lyric.py" solltest Du die Behandlung von Dateien dringend verbessern, denn auch diese Datei gilt als Beispiel, wie man es nicht machen sollte:

Code: Alles auswählen

fp = os.path.join(cfg[LYRICS_DIR], "%s-%s.txt" % (self.artist, self.title))
if os.path.isfile(fp):
    try:
        f = open(fp, 'r')
        lyric = f.read()
        f.close()
        return lyric
    except Exception, e:
        print("Lyrics Datei nicht gefunden: %s" % (fp))
return ''
Die Prüfung in der zweiten Zeile ist vollkommen überflüssig, da "open" eine Ausnahme auslöst, wenn die Datei nicht existiert. Dateien öffnet man mithilfe der "with"-Anweisung, um sicherzustellen, dass sie auch im Falle einer Ausnahme korrekt geschlossen werden. Die Ausnahmebehandlung ist an dieser Stelle mit Verlaub mies. Die Fehlermeldung ist falsch, denn eine Ausnahme kann auch aus anderen Gründen auftreten. Außerdem ist sie deplaziert, weil die Ausnahme hier noch gar nicht sinnvoll behandelt werden kann. Die Ausnahme muss nach oben durchgereicht werden, bis zu einer Stelle, wo man sie sinnvoll behandeln kann.

"mp3.py" halte ich für vollkommen überflüssig. Der einzige Zweck dieser Datei scheint mir die Abbildung der ID3-Frames auf generische Tag-Namen, was "mutagen" auch selbst kann:

Code: Alles auswählen

>>> import mutagen
>>> mutagen.version
(1, 20)
>>> metadata = mutagen.File('01 - Colossal.mp3', easy=True)
>>> metadata['artist']
[u'Wolfmother']
>>> metadata['album']
[u'Wolfmother']
>>> metadata['title']
[u'Colossal']
Insofern scheint mir diese Datei überflüssig. Wie dem auch sei, ändern sollte man die Fehlerbehandlung in dieser Datei. Statt im Falle fehlender Tags Fehlerwerte wie -1 zurückzugeben, löst man besser eine Ausnahme aus, die man dann aber bitte auch richtig behandelt.

Den Rest des Quelltexts habe ich mir nach allem, nicht mehr angetan, denn ehrlich gesagt war das, was ich gesehen habe, schlecht genug, um mich Abstand halten zu lassen. Verzeihe mir bitte die offenen Worte, es tut mir auch leid, dass dies sicherlich keine Kritik ist, die man gerne hört, aber Du hattest darum gebeten, und das ist alles, was ich dazu sagen kann.
Benutzeravatar
Matflasch
User
Beiträge: 41
Registriert: Donnerstag 25. März 2004, 15:42
Wohnort: Hamburg
Kontaktdaten:

Danke für die Kritik! Auch wenn man sowas evtl. nicht gerne hört, wollte ich genau sowas hören. Ich habe die meisten Programme bisher in Java und davor in PHP geschrieben (machen müssen), da habe ich mir wohl die eine oder andere nicht so schöne Sache angeeignet... Das setup.py Skript steht noch aus, ich wollte an einigen Stellen wohl mal wieder mit dem Kopf durch die Wand und habe dadurch nicht so schönen Code, und z.B. keine setup.py..
Aber wo werden denn Leerzeichen und Tabulatoren gemischt? Das ist mir bisher nicht aufgefallen. Ich benutze Netbeans, welches eigentlich jeden Tabulator durch 4 Leerzeichen ersetzt...?

Was die Namensgebung für Methoden und Variablen angeht, das ist bei mir definitiv verbesserungswürdig, das stimmt. Die Metadaten (__version__...), die stehen auch erst seit gestern in allen Dateien, kommen wieder raus. Habe das ähnlich im inet gefunden und mir einfach ein Beispiel daran genommen.

Das Exception-Handling mit "except TypeError: pass" in collection.py ist so vorgesehen, weil 0 als Standard-Rückgabewert dient. Sobald eine ID für einen DB-Eintrag gesucht wird und diese ID noch nicht existiert, werden die entsprechenden Werte eingetragen und danach wird die ID zurück gegeben. Der TypeError ist hier vorgesehen, falls der Eintrag in der DB nicht gefunden wurde. Evtl. nicht schön, aber an dieser Stelle hielt ich es für angebracht.

Es ging mir bisher auch sehr darum mich in ein GUI-Toolkit einzuarbeiten, ebenso Python besser kennen zu lernen. Qt und Phonon werde ich mir als nächstes anschauen. Danach bin ich mal gespannt, ob ich bei wx bleibe oder ob es Qt wird.

Viele Kritikpunkte habe ich stillschweigend akzeptiert und sehe ein, dass ich viele Stellen oder generelle Dinge wie Exceptions besser definieren sollte.
Mit Qt werde ich versuchen, viele Dinge besser zu machen :)
lunar

Matflasch: Das kannst Du selbst herausfinden, indem Du "tabnanny" über den Quelltext laufen lässt:

Code: Alles auswählen

$ python2 -m tabnanny pymp
pymp/src/pympapp.py 733 '\tm,s = divmod(t, 60)\n'
pymp/src/pymp/player.py 212 '\ts,ns = divmod(t, 1000000000)\n'
pymp/src/pymp/listctrl.py 154 '\tm,s = divmod(t, 60)\n'
Das sind in der Tat nur drei Stellen, insofern doch nicht allzu tragisch.
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

the more they change the more they stay the same
Antworten