Erster Gehversuch in Python: Link-Decrypter

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Antworten
tschan
User
Beiträge: 5
Registriert: Freitag 30. März 2007, 16:10

Hi,

ich beschäftige mich seit einiger Zeit mit Python und möchte an dieser Stelle die ersten sinnvollen Früchte meiner Arbeit präsentieren. Wahrscheinlich habe ich unzählige Anfängerfehler gemacht, aber ich hoffe, dass ihr mir helft, diese auszumerzen.

Erstmal: Was soll das Programm machen?
Möglicherweise ist einigen von euch ja die Existenz solcher Linkverschlüsselungsdienste wie "Rapidsafe.de" oder "xeem.to" bekannt, die hauptsächlich dafür genutzt werden, Rapidshare-Links zu verschleiern. (Wenn nicht, ist auch egal :D ).
Ich wollte jetzt ein Script schreiben, das es mir erlaubt eine gegebene Liste von verschlüsselten Links zu entschlüsseln.
Dabei sollten die einzelnen unterstützten Dienste als einfach zu ergänzende Plugins vorliegen.
Desweiteren wollte ich dem Pluginautor so viel Arbeit wie möglich abnehmen, sodass sich dieser wirklich nur noch um die Entschlüsselung des betreffenden Dienstes kümmern muss.

Wie ist der momentane Stand, wo sehe ich noch Verbesserungsmöglichkeiten?
  • Das Grundgerüst steht und ist funktionsfähig. Ich habe allerdings für noch nicht sonderlich viele Verschlüsselungsdienste Plugins geschrieben. Und auch diejenigen Dienste, die ich unterstütze, sind alles andere als schwierig zu knacken. Mir ging es jetzt aber erstmal darum, einen vernünftigen Rahmen zu schaffen. Plugins können später immernoch ergänzt werden.
  • Meine "functions.py" ist ein ziemliches Schlachtfeld. Da muss noch mehr Ordnung rein. Desweiteren muss ich meine FakeBrowser() Implementierung nochmal überarbeiten (die ist entstanden, als ich annahm, dass "urlopen" das Extrahieren der Header-Daten nicht unterstützt). Auch die GUI-spezifischen Teile muss ich mir nochmal ansehen.
    Man könnte sagen: "functions.py" ist natürlich gewachsen. Dementsprechend sieht sie auch aus. :D
  • Generell könnte der Sourcecode noch ein paar Kommentare mehr vertragen. Auch wenn ich denke, dass "decrypter.py" ausreichend kommentiert ist.
Den derzeitigen Stand könnt ihr euch hier runterladen.

Wer das nicht möchte: So sieht die Verzeichnisstruktur im Augenblick aus:
./include/functions.py
./plugins/link-protector_com.py
./plugins/rapidsafe_net.py
./plugins/rapidshare_master.py
./decrypter.py

Ich freu mich auf eure Kritik.

Gruß
tschan
BlackJack

Die Annahmen dass der Pfad zum Skript immer als erstes in `sys.path` steht, und dass eine Datei sofort vom Garbage Collector abgeräumt wird und man deshalb kein explizites `close()` braucht, sind IMHO gewagt. Das sind beides Implementierungsdetails, die sich jederzeit ändern können.

Zum Verbinden von Pfadelementen würde ich `os.path.join()` der "manuellen" Konkatenation mit `os.sep` vorziehen.

Für Plugins ist es sinnvoll `sys.path` zu manipulieren, aber `functions` hätte man in ein Package stecken können. Oder eben nicht in ein Unterverzeichnis, sondern auf die gleiche Ebene wie `decrypter`.

Dann ist die Namensgebung sowohl von den Konventionen als auch von der Bedeutung nicht immer einwandfrei. Zum Beispiel kommt sehr häufig der Postfix 'list' bei Namen vor die an Dictionaries gebunden sind. Da würde ich den irreführenden "Typzusatz" ganz weglassen und einfach nur die Bedeutung in den Namen packen, also `plugins` statt `pluginlist` oder `instances` statt `instancelist`. Und `plugin_instance` sagt auch nicht viel mehr aus als `plugin`. Gleiches gilt für den Namenszusatz `class`. Klassen erkennt man an der Konvention das die Namen in MixedCase geschrieben sind. Wobei der Unterschied Klasse vs. Funktion auch nur wirklich wichtig ist, wenn man eine Unterklasse von dem Objekt ableiten will, ansonsten sind beides "callables".

`decrypt_main()` ist mir viel zu kompliziert. Die ist zu lang zu tief verschachtelt und da merkt man das irgendwo Dokumentation fehlt, die einen Überblick über den Entwurf bietet.

Von der Funktion aus wird auch viel zu tief in andere Datenstrukturen "durchgegriffen". So etwas wie ``plugin_instance.instancelist[name].url_list.append(link)`` wird schnell sehr unübersichtlich weil man beim lesen bei jedem Punktoperator erst einmal klären muss was für ein Objekt man an der entsprechenden Stelle eigentlich hat. Ausserdem hat man beim "Durchgriff" seine Finger in Objektinterna, die man manchmal besser dem Objekt selbst überlässt. Es ist zum Beispiel nicht möglich im `hoster_class`-Objekt von einer Liste auf ein `set()` umzustellen, ohne das der Client-Code davon unberührt bleibt.

Eine Menge Punktoperatoren kann man in der Funktion einsparen, wenn man in der äussersten Schleife nicht über die Namen, sondern gleich über die Objekte iteriert.

Bei den Operationen, die auf den Links durchgeführt werden, bieten sich `set()`\s statt Listen an, wenn ich das richtig sehe.

Bei Threads ist das `threading`-Modul etwas komfortabler als das eher Low-Level `thread`-Modul. Die `exitmutexes` kann man wunderbar mit `join()`-Methode ersetzen. Womit auch die `hoster_class_master.thread_decrypt()`-Methode überflüssig wird.

Wenn man die Ausgabe in ein `StringIO`-Objekt umleitet, sammelt sie sich im Speicher an. Falls das zu einem Problem werden kann, gibt's unter `os.devnull` einen Dateinamen von einer Datei in der alles verschwindet. Das Original `sys.stdout` muss man sich nicht selbst merken, das steht auch unter `sys.__stdout__` noch einmal zur Verfügung.

Auf zu den `functions`. Eine `GET`-Methode die `POST` aufruft und in `POST` unter Umständen eine `GET`-Anfrage absetzt sieht verwirrend und unsauber aus.

Die drei `get_*`-Methoden vom `FakeBrowser` kann man weglassen und stattdessen gleich auf die Attribute zugreifen.

Die beiden Funktionen für Passwort bzw. Captcha sind nahezu identisch und können durch eine ersetzt werden. Der Name `StringIO_Object` sagt viel zuviel über den Typ und gar nichts über die Bedeutung des Objekts. `captcha_file` wäre zum Beispiel passender.

Das `master`-Argument würde ich an der ersten Stelle in der Argumentliste belassen, das wird bei Widgets an der Stelle erwartet.

Bei vielen Widgets wird die Erzeugung auf mehr Zeilen verteilt als nötig ist, weil man die Informationen oft als Schlüsswelwortargumente bei der Initialisierung mitgeben kann. Zum Beispiel den Text bei `Label`-Objekten.

Beim `Button` benutzt man normalerweise das `command`-Argument für eine Funktion die beim Drücken ausgeführt werden soll.

`hex_unescape()` kann man einfacher schreiben:

Code: Alles auswählen

In [22]: '507974686f6e'.decode('hex')
Out[22]: 'Python'
Bei der Ausgabe von jeweils nur einer Zeile ist Sperren überhaupt nicht nötig. Das `lock` ist IMHO auch an der falschen Stelle. Das Objekt das geschützt werden soll ist `sys.stdout`, darum gehört auch das `lock` eher in dessen "Nähe" und nicht in die `hoster_class_master`-Klasse.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Hallo tschan, willkommen im Forum,

Du hast so wie ich das sehe das Pluginsystem ähnlich wie What's On Air nur unübersichtlicher. Wenn du dir den ``PluginController`` in whatsonair.py ansiehst, dann hast du eine Klasse die alle Plugins selbst lädt und verwaltet. Die Plugins können in einer for-Schleife direkt idetirert werden und erben vom Base-Plugin auch viele nützliche Sachen (wie übrigens bei dir auch). Natürlich ist die Implementation von What's On Air nicht optimal, aber sie hat sich bisher ziemlich gut bewährt. Ich habe nur keine Zeit/Lust dort einige Dinge aufzuräumen, jedoch sehe ich es als großen Vorteil an, dass die Plugins auch ohne Hauptprogramm aufrufbar sind und somit sehr einfach zu entwicklen und zu testen sind.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
tschan
User
Beiträge: 5
Registriert: Freitag 30. März 2007, 16:10

Hallo,

erstmal vielen Dank für die Arbeit, die ihr euch gemacht habt.
Ich werde alle Kritikpunkte in Ruhe abklappern. Ich sehe schon, das wird ein hartes Stück Arbeit. :)
Eine Sache zum "threading" Modul: Das wollte ich zuerst nutzen, allerdings kann es sein, dass ich ein und die selbe Entschlüsselungsfunktion bei mehreren Schleifendurchläufen starten muss (beispielsweise, wenn ein Link erst mit Dienst A und dieser verschlüsselte Link dann nochmal mit Dienst B verschlüsselt wird). Da streikt das Modul aber und wirft mir eine Exception.

Gruß
tschan
BlackJack

Was für eine Exception? Da musst Du etwas falsch benutzen; es ist auf jeden Fall möglich die gleiche Funktion in mehreren Threads zu starten.
tschan
User
Beiträge: 5
Registriert: Freitag 30. März 2007, 16:10

Hallo,

vielleicht missverstehen wir uns auch. Wenn ich eine Klasse erstelle, die von threading.Thread abgeleitet ist und diese, nachdem sie einmal gestartet und wieder beendet worden ist, nochmal starten möchte, erhalte ich die Exception "thread already started".
Ein Code Beispiel:

Code: Alles auswählen

import time,threading

class Count(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
    
    def run(self):
        for x in range(5):
            time.sleep(1)
            print x+1

threads = []
for x in range(5):
    thread = Count()
    threads.append(thread)

while True:
    for thread in threads:
        thread.start()
    
    for thread in threads:
        thread.join()
    
    if raw_input() == 'q': break

print 'main thread exited'
Beim zweiten Durchlauf der Schleife wirft er mir die oben genannte Exception. Ich kann also ein und die selbe Thread-Instanz nur einmal starten.
Dass ich das umgehen kann, indem ich die Erstellung der Instanzen auch mit in die Schleife packe, ist mir klar. Ist es aber sinnvoll, das zu tun?

Gruß
tschan
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

tschan hat geschrieben:Ist es aber sinnvoll, das zu tun?
Hi tschan!

Diese Frage wurde vor kurzem in der python-de-Mailingliste beantwortet.

http://python.net/pipermail/python-de/2 ... 08133.html

mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
BlackJack

Man muss ja nicht von `Thread` erben, man kann mit `Thread`-Objekten auch beliebige Funktionen starten.

Für meinen Geschmack werden in dem decrypter sowieso Objekte zu oft wiederverwendet. Anstelle ein Objekt mit einer `clear()`-Methode wieder in einen "sauberen" Zustand zu versetzen, würde ich einfach ein neues Objekt erzeugen. Auf lange Sicht ist das weniger fehleranfällig.
Antworten