pypack

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Antworten
Manuelh87
User
Beiträge: 36
Registriert: Sonntag 15. März 2009, 16:24

Hi!

Hab so ein kleines (und überhaupt nicht hübsches) skriptchen, dass aber vielleicht auch für andere brauchbar ist... wer weiß... also:

sicher viele kennen die .jar files von java: mehrere .class files kommen da rein, und die informationen was zu starten ist...
Ja, aus aktuellem anlass wollt ich sowas auch für python, und so ist pypack entstanden... schnell, unsauber, aber überzeugt euch selbst...

man rufts so auf: ./pypack.py main-file [included-file] [included-file]
und es erzeugt eine datei packout.py die dann alle übergebenen datein in einem archiv beinhaltet und den code des main-file beinhaltet (ja richtig, das main-file ist doppelt vorhanden... ist so gewollt ;P)

Es geht darum: Soll ich das ganze nochmal hübsch machen und mir richtig was überlegen (es geht um zusätzliche optionen, etc.) oder ist es eh weniger sinnvoll und nützlich und ich kann das ruhig lassen.. ;P

Gebraucht hab ich das ganze da ich ein komplizierteres Programm (aus vielen einzelnen .py dateien) mit gui designed hab und es auch dem typischen mac-user zur verfügung stellen wollte... mir ist natürlich klar dass es keineswegs erforderlich ist dass das ganze projekt nur aus einer datei besteht... aber der typische mac-user tut sich glaub ich mit nur einer datei leichter... wie auch immer.. nur falls ihr euch fragt: "Was hat er sich nur dabei gedacht, wozu soll das gut sein" :)
(Anmerkung: ich will kein installier-packet machen müssen, mir ist klar dass das der übliche weg wäre, aber es soll möglichst unkompliziert sein... und es ist ne sehr spezielle anwendung die für so gut wie niemanden außer einen kleinen kreis von personen interessant ist....)

Nagut, genug der Worte... hier der Python-Code:

Code: Alles auswählen

#!/usr/bin/env python

"""
	version: 0.1
"""

import zipfile
import tempfile
import sys
import os

READ_SIZE = 1000

def read2mem(file_obj):
	
	data = []
	rb = file_obj.read(READ_SIZE)
	while rb != '':
		data.append(rb)
		rb = file_obj.read(READ_SIZE)
	
	return "".join(data)
		

def main(main_file, src_files, dest = 'packout.py'):
	
	os_handle, zip_path = tempfile.mkstemp()
	os.close(os_handle)
	
	zf = zipfile.ZipFile(zip_path, 'w')
	
	for pyfile in src_files:
		arc = os.path.split(pyfile)
		zf.write(pyfile, arc[1])
	
	zf.close()
	
	zf = open(zip_path, 'r')
	data = read2mem(zf)
	zf.close()
	
	os.remove(zip_path)
	
	df = open(dest, 'w')
	df.write("#!/usr/bin/env python\n\n")
	df.write("import tempfile\nimport os\nimport sys\n\n")
	df.write("pypack_data = " + data.__repr__() + "\n")
	df.write("pypack_handle, pypack_zip_path = tempfile.mkstemp()\n")
	df.write("os.close(pypack_handle)\n")
	df.write("f = open(pypack_zip_path, 'w')\n")
	df.write("f.write(pypack_data)\n")
	df.write("f.close()\n\n")
	df.write("sys.path.insert(0, pypack_zip_path)\n\n")
	df.write("try:\n")
	
	f = open(main_file, 'r')
	
	for line in f:
		df.write("\t" + line)
	
	f.close()
	
	df.write("\nfinally:\n")
	df.write("\tos.remove(pypack_zip_path)\n")
	df.close()
	
if __name__ == '__main__':
	if len(sys.argv) > 2:
		# just add the main file to the packet, who cares...
		main(sys.argv[1], sys.argv[1:])
	else:
		print "pypack [main-filename] [other files]"
mfg Manuel

Anmerkung: Ich hab das Programm bisher nur auf linux getestet, wollte zwar auf mac auch aber meine liebe schwester hatte angst es zerstört ihren kleinen mac.... und auch nach diversen erklärungen war da noch immer die angst... tja... ;P
Und für windows war ich zu faul (hätt ich erst python installieren müssen... bäh... :) )
Aber wär toll wenn ihr mir Berichten könntet falls irgendwas auf irgendeiner plattform nicht funktioniert. Und vielleicht auch nachsehen ob die angelegten tmp dateien eh brav gelöscht werden... und unter welchen umständen das fehlschlägt....
BlackJack

@Manuelh87: `read2mem()` ist zu umständlich. Wenn Du sowieso die gesamte Datei in den Speicher lesen willst, ist es sinnfrei das in kleinen Blöcken zu tun und die dann am Ende zusammen zu fügen, da kannst Du auch gleich mit *einem* `read()` die gesamte Datei einlesen.

Anstelle der vielen `write()`-Aufrufe hätte ich eine grössere Zeichenkette mit der Vorlage des Programms geschrieben und nur *ein* `write()` verwendet.

Man kann im Hauptprogramm keine `__future__`-Importe verwenden, weil die vor jeder anderen Anweisung im Quelltext stehen müssen.

Du rückst das eingefügte Hauptprogramm mit Tabs ein. Das führt zu Problemen, wenn das Hauptprogramm, wie empfohlen, mit Leerzeichen eingerückt ist. Ich glaube in Python 3 ist das sogar ein `SyntaxError`, wenn man Tabs und Leerzeichen in der gleichen Zeile zum Einrücken verwendet.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Manuel,

auf dem Mac ist das, was der normale Anwender im Finder sieht, sowieso ein Verzeichnis. Ob darin nun 1000 Python-Dateien enthalten sind oder nicht fällt dem Anwender nicht auf. Und Python gibt es auf dem Mac ja auch schon. Wenn also nur dafür ist, gibt es IMHO besser Wege.

Wenn ich das richtig sehe, fügst du die Zip-Datei brutal in einen Byte-String ein, sodass jedes Zeichen außerhalb von ASCII als `\xXX` dargestellt wird. Ist das wirklich kürzer als base64? Zudem würde letzteres automatisch noch nett umgebrochen werden. Diese Zip-Datei wird dann beim Start wieder zu einer Datei, in den `sys.path` eingebunden und dann das eigentliche Programm ausgeführt.

Ich finde unschön, dass so der Interpreter einmal die komplette Zip-Datei im Hauptspeicher hält und wahrscheinlich die Stringkonstante auch nicht freigibt, selbst wenn du noch `pypack_data = None` schreiben würdest.

Das Kopieren ist nicht nötig, denn damit selbstausführende ZIP-Dateien funktionieren, erlaubt der Standard, dass da am Anfang beliebiger Müll drin stehen kann. Mit Python geht das leider wohl nicht, denn der Interpreter besteht darauf, bis zum Ende der Datei alles zu lesen. Aber es muss ja nicht Python sein. Mache doch einfach ein Shellscript daraus:

Dies ist mein Startscript `starter`.

Code: Alles auswählen

#!/bin/sh
python -c "import sys;sys.path.insert(0,'$0');import main"
exit
Packe jetzt alles in die Zip-Datei `stuff.zip`. Da muss es eine Datei `main.py` geben, in der das Hauptprogramm steht. Dann:

Code: Alles auswählen

cat starter stuff.zip > application
chmod +x application
Jetzt kann man `application` wie jedes andere Programm aufrufen.

Zu `read2mem` fällt mir noch ein, dass diese Funktion extrem ineffizient ist und nichts anders macht als `file_obj.read()` auch tun würde. Somit kannst du die Funktion auch weglassen. Deine Funktion `main` würde ich `pack` nennen.

Stefan
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Wenn es dir um eine einfache Distribution geht, warum nutzt die nich pyinstaller, py2app, py2exe, etc., statt deinem doch ehrlich gesagt abenteuerlichem Ansatz?

Außerdem würdest du dich damit unabhängig vom Python-Interpreter machen, der bei Mac OS hinterherhinkt - bei den meisten Linux-Distributionen natürlich auch und von Windows wolln wa ma gar nich anfangen ;)
Manuelh87
User
Beiträge: 36
Registriert: Sonntag 15. März 2009, 16:24

Erstmals bedank ich mich für die Antworten! Was vielleicht ein bisschen missverstanden wurde: Es geht mir darum sowas zu schaffen wie jar bei java, also eine datei, plattformunabhängig, braucht nur den python interpreter und das wars, und läuft so auf allen systemen wo solch einer installiert ist.

Das 2. wäre dass der code natürlich unsauber ist, dass ist mir völlig klar, es geht mir ja genau darum ob das was ich da gemacht hab eben brauchbar ist (eurer meinung nach) oder nicht, denn wenn nicht, brauch ichs auch nicht erweitern und hübscher machen.

@ BlackJack: Ja danke, das man einfach mit read() alles einlesen kann hab ich übersehen... aber ich programmiere erst seit ein paar Monaten mit python und hab das wohl mit recv() von socket verwechselt (wo man glaub ich immer sagen muss wieviel man einlesen will) aber danke für den hinweis, das ersparrt die recht unschöne funktion...
Ich hab in letzter Zeit so viel python-docu gelesen... ;P also sry
naja es wäre durchaus denkbar auch dieses __future__ einzubauen... aber eben nur wenn es auch irgendwer außer mir als brauchbar empfindet... eben mit zusätzlichen optionen usw.
Ja, das mit den Leerzeichen hab ich schonmal wo gelesen, aber um das zu ändern muss ich das ja nur meinem editor sagen... Ich programmiere eben normalerweise eher in C++ und C und ASM und da verwend ich überall tabs, deshalb hab ich mir das nicht abgewöhnt. Wenn ich pypack aber wirklich weiterentwickeln sollte werd ich das auf jeden Fall genau so machen wie du das beschrieben hast, und überall brav doc-strings dazukleben, wie man das eben machen sollte ;)
Bezüglich des writes und so... naja ich sags mal so: ich verwende python nicht weil es so unglaublich schnell ist... also dem anwender ists glaub ich egal ob es 2ms länger dauert oder kürzer... zu groß sollte das projekt sowieso nicht sein, wegen der größe der zip datei die ja hartgecoded im source-file steckt.

@ sma: Also sry, aber deinen ansatz versteh ich nicht: Es geht ja darum ein packet zu generieren dass nur python braucht und sonst nix. Wenn ich das mit shell-script mach, ist das wohl weniger günstig. Und wenn sich mein mac-user da die richtige python datei aussuchen muss ist das auch suboptimal. Es soll eben sehr einfach für den end-user sein...

@ cofi: Naja, du hast schon recht, das ganze ist alles andere als sauber und schön. Aber der große Vorteil wäre eben dass ich nicht eine extra win version, eine extra mac und eine extra linux version machen muss... (obgleich das natürlich kein riesen aufwand ist)


Meine Frage am Ende wäre halt, ob irgendwer denkt dass es ein sinnvolles Projekt ist. Bisher hat das eher nicht so geklungen... Ich mein ich nehm das ja nicht persöhnlich oder so, es geht mir wirklich nur darum ob ich das weiterentwickeln sollte oder nicht... Deshalb hab ich mir eben auch keine Gedanken bis jetzt gemacht wegen Performance etc....

mfg Manuel
BlackJack

@Manuelh87: Mir ging es bei den vielen `write()`\s nicht um die Ausführungsgeschwindigkeit, sondern das ich das einfach unelegant finde so viele male das gleiche zu tippen, wo einmal genügen würde.

Und wenn Du so etwas wie ein *.jar haben möchtest, dann brauchst Du ab Python 2.6 gar nichts selber Programmieren, sondern musst nur alles in ein ZIP stecken, mit einer `__main__.py` auf der obersten Ebene, und kannst das dann einfach mit ``python programm.zip`` ausführen. Die Endung *.pyz geht auch, die kann man dann mit dem Python-Interpreter verknüpfen.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Für ältere Versionen gab es diesen Preloader für Eggs, dessen Namen ich vergessen habe. BlackJack weiß da bestimmt näheres.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Manuelh87 hat geschrieben:@ sma: Also sry, aber deinen ansatz versteh ich nicht: Es geht ja darum ein packet zu generieren dass nur python braucht und sonst nix. Wenn ich das mit shell-script mach, ist das wohl weniger günstig. Und wenn sich mein mac-user da die richtige python datei aussuchen muss ist das auch suboptimal. Es soll eben sehr einfach für den end-user sein...
Dein Mac-User hat in jedem Fall eine Shell (sogar in X Geschmacksrichtungen: sh, bash, csh, ksh, zsh) und mein Ansatz ist IMHO Enduser-tauglich, denn es entsteht ja nur eine Datei, er muss da nichts heraussuchen. Ein Terminal ist natürlich notwendig, genau wie in deinem Fall (du wolltest den Benutzer ja "packout.py" aufrufen lassen). Wenn's doppelklickbar sein soll, musst du in beiden Fällen mehr tun (ein Doppelklick auf eine .py-Datei öffnet bei mir jedenfalls TextMate und führt sie nicht aus).

Also ich finde meine Ansatz sehr einfach und elegant :) Was ich ja nur zeigen will ist, dass man sehr einfach Zip-Dateien ausführbar machen kann.

Und als Bonus funktioniert meine Version übrigens genauso unter jedem anderen Unix-artigen Betriebssystem wie z.B. Linux.

Stefan
Benutzeravatar
str1442
User
Beiträge: 520
Registriert: Samstag 31. Mai 2008, 21:13

Kleine Anmerkungen:

Code: Alles auswählen

    rb = file_obj.read(READ_SIZE)
    while rb != '':
        data.append(rb)
        rb = file_obj.read(READ_SIZE) 
1. Kannst du hier besser mit einer while True: ... if rb == "": break Schleife arbeiten und sparst dir einmal den duplicated Code von rb =
2. Gibt es das, was du da zu bauen versuchst, schon in den Builtins: iter() nimmt als alternative Aufrufform ein Callable an, welches jedesmal einen Teil der neuen Sequenz "yielden" soll und ein "sentinel" Argument, welches festlegt, wann abgebrochen werden soll. Du kannst also vereinfachen zu:

Code: Alles auswählen

for packet in iter(lambda: file_obj.read(READ_SIZE), ""):
    ...
(oder auch partial(file_obj.read, READSIZE) :D)

Das Modul optparse ist für Shellprogramme recht nützlich.

Desweiteren gibt ein Modul "zipimport", mit dessen Hilfe man scheinbar zip Dateien irgendwie importieren kann, kenne ich aber nur vom drüberschauen aus der Stdlib.
Antworten