Python Scripte mit PyInstaller als .exe verteilen
Wozu der Aufwand?
Es kommt der Tag, an dem man seine Python Scripte ausserhalb seiner Entwicklungsumgebung einsetzen möchte. Von anderen Programmiersprachen kennt man die Möglichkeit, die Programme zu compilieren und diese unter Windows als ausführbare .exe oder .dll Dateien zu verteilen.
Compilieren im eigentlichen Sinn lässt sich ein Python Script nicht. Man kann jedoch Python Scripte auch in .exe verwandeln und diese verteilen. Der Grund dafür kann sein, daß man nicht überall eine installierte Python-Umgebung voraussetzen möchte oder auch um seinen Quellcode gegen neugierige Blicke oder Änderungen zu schützen. In diesen .exe-Dateien wird dann neben dem Script noch eine Python-Laufzeitumgebung und benötigte Bibliotheken mit eingepackt, so daß alle benötigten Bestandteile dabei sind und das Programm sofort auf dem Zielrechner ausführbar ist.
Welche Möglichkeiten gibt es?
Es gibt mehrere Möglichkeiten, aus Python Scripten solche ausführbaren Programme zu erstellen:- PyInstaller (Für Linux und Windows, alle Python-Versionen seit 1.5, wird noch aktiv entwickelt, kann eine einzelne Datei erstellen)
- py2exe (wird oft und gerne benutzt, hat mir persönlich aber ein paar Einschränkungen, die man beim Programmieren beachten muss, hat Schwierigkeiten mit manchen Bibliotheken)
- McMillan's Installer (aus dem PyInstaller entstand)
Beispielhafte Anwendung mit PyInstaller
1. PyInstaller herunterladen und entpacken
PyInstaller gibt es als .zip Archiv für Windows auf der Projekthomepage zum Download. Aktuell beim Verfassen dieses Artikels ist die Version 1.2. Die herunter geladene Archivdatei pyinstaller_1.2.zip entpackt man in einem Pfad seiner Wahl. Ich gehe im Folgenden davon aus, daß dies C:\Python24\PyInstaller_1.2\ ist.2. PyInstaller konfigurieren
PyInstaller muss vor der ersten Verwendung auf dem System konfiguriert werden. Damit stellt PyInstaller fest welche Version von Python verwendet werden soll, welche Optionen verfügbar sind, ob Zlib, Unicode oder UPX verfügbar sind und ein paar Dinge mehr. Das ist allerdings recht einfach, da PyInstaller selbst diese Konfiguration vornimmt.
- C:\> cd C:\Python24\PyInstaller_1.2\
- C:\Python24\PyInstaller_1.2> Configure.py
- I: computing EXE_dependencies
- I: Finding TCL/TK...
- I: found TCL/TK version 8.4
- I: testing for Zlib...
- I: ... Zlib available
- I: Testing for ability to set icons, version resources...
- I: ... resource update available
- I: Testing for Unicode support...
- I: ... Unicode available
- I: testing for UPX...
- I: ...UPX unavailable
- I: computing PYZ dependencies...
- C:\Python24\pyinstaller_1.2>
Damit ist die Konfiguration abgeschlossen. PyInstaller ist jetzt bereit, auf diesem System ausführbare Dateien zu erstellen.
3. Spec-File erstellen
Eine .spec-Datei beschreibt das später zu erstellende Programm. Das Testprogramm ist eine einfache helloworld.py im Ordner C:\Test\:- #!/usr/bin/env python
- print "hello world!"
Für dieses einfache Programm wird nun die .spec-Datei erstellt:
- C:\Test> C:\Python24\PyInstaller_1.2\Makespec.py helloworld.py
- wrote C:\test\helloworld.spec
- now run Build.py to build the executable
Danach steht im gleichen Ordner zusätzlich unsere .spec-Datei, die Anweisungen für den eigentlichen PyInstaller enthält:
- a = Analysis([os.path.join(HOMEPATH,'support\\_mountzlib.py'), os.path.join(HOMEPATH,'support\\useUnicode.py'), 'helloworld.py'],
- pathex=['C:\\test'])
- pyz = PYZ(a.pure)
- exe = EXE(pyz,
- a.scripts,
- exclude_binaries=1,
- name='buildhelloworld/helloworld.exe',
- debug=False,
- strip=False,
- upx=False,
- console=True )
- coll = COLLECT( exe,
- a.binaries,
- strip=False,
- upx=False,
- name='disthelloworld')
Diese Datei kann nun auch bearbeitet werden um zum Beispiel weitere Ressourcen hinzuzufügen, zum Beispiel config-Dateien, Icons, Bilder und sonstige Dateien die für den späteren Betrieb notwendig sind.
Makespec.py kennt diverse Optionen um den Prozess zu beeinflussen, wovon man jedoch in den meisten Fällen nur einige benötigt:
- -F, --onefile
- Erstellt eine einzelne Datei, die alles beinhaltet.
- -D, --onedir
- Erstellt ein Verzeichnis mit allen benötigten Dateien (default).
- -w, --windowed, --noconsole
- Das zu erstellende Programm wird als Windows Programm gestartet
- ohne eine Konsole dafür zu öffnen.
- -o DIR, --out=DIR
- Erstellt die .spec-Datei im angegebenen Verzeichnis. Wenn nicht
- angegeben und das aktuelle Verzeichnis ist das PyInstaller-
- Verzeichnis, wird ein Unterverzeichnis angelegt und dort die
- .spec Datei hineingeschrieben. Steht man in einem anderen
- Verzeichnis, so wird die .spec-Datei in das aktuelle Verzeichnis
- geschrieben.
- --icon=<file.ico>
- Fügt dem zu erstellenden Programm das Icon FILE.ICO hinzu.
- --icon=<file.exe,n>
- Fügt dem zu erstellenden Programm das N-te Icon aus FILE.EXE
- hinzu.
- -n NAME, --name=NAME
- Gibt dem Programm einen Namen. Wenn dieser Name nicht angegeben
- wird bekommt das zu erstellende Programm den Namen des ersten
- angegebenen Scripts.
4. Ausführbare .exe erstellen
Zu diesem letzten Schritt zieht man nun die Build.py aus dem PyInstaller-Ordner heran und füttert sie mit der oben erstellten .spec-Datei:- C:\test> c:\Python24\pyinstaller_1.2\Build.py helloworld.spec
- checking Analysis
- building Analysis because out0.toc non existent
- running Analysis out0.toc
- Analyzing: c:\Python24\pyinstaller_1.2\support\_mountzlib.py
- Analyzing: c:\Python24\pyinstaller_1.2\support\useUnicode.py
- Analyzing: helloworld.py
- Warnings written to C:\test\warnhelloworld.txt
- checking PYZ
- rebuilding out1.toc because out1.pyz is missing
- building PYZ out1.toc
- checking PKG
- rebuilding out3.toc because out3.pkg is missing
- building PKG out3.pkg
- checking ELFEXE
- rebuilding out2.toc because helloworld.exe missing
- building ELFEXE out2.toc
- checking COLLECT
- building out4.toc because out4.toc missing
- building COLLECT out4.toc
Nach diesem Schritt existieren zwei weitere Verzeichnisse unterhalb von C:\Test\:
C:\Test\buildhelloworld\ (wurde benutzt um die Dateien zusammenzuführen)
C:\Test\disthelloworld\ (enthält jetzt das weiterzugebende Programm inklusive aller benötigten Bestandteile)
Den Inhalt des Ordners disthelloworld kann man nun weitergeben und auf jedem Windows-System ausführen.





#1 2006-12-26 13:06 (Reply)
Hi Namensvetter, ist diese Exe-Fassung bei allen Python Skripten möglich? Also z.B. auch bei jenen, die eine GUI verwenden? Ciao, M
#1.1 2006-12-26 13:17 (Reply)
hi namensvetter, ja, geht auch bei gui-verwendung genauso. dann verwendet man allerdings --noconsole als option, damit man nicht immer ein terminalfenster im hintergrund hat.
#2 2006-12-26 14:31 (Reply)
Kannst Du mir eventuell auch verraten, um wieviel die .exe circa (!) größer wird? (Evtl. gibt es ja einen "festen Bestandteil", den man immer zur Skriptgröße addieren kann.) Vielleicht hast Du ja einen Pi-mal-Daumen-Erfahrungswert. Und noch was: Gibt es denn einen großen Performance-Unterschied, zwischen .exe und dem direkt ausgeführten Skript? Sorry für die Fragerei, aber ich suche gerade nach einer neuen Programmiersprache und irgendwie bin ich zur Zeit bei Python hängengeblieben.
#2.1 2006-12-26 17:50 (Reply)
um wieviel sie größer wird ist schwer zu beantworten, weil das vom verwendeten GUI toolkit abhängt. bei wxpython sind es ein paar mb (ca. 2-6 je nach verwendeter kompression), weil die verwendete dll eben diese paar mb mitbringt. bei tkinter ist es weniger, sagt mir aber von der optik nicht zu. bei "venster" ist es kaum der rede wert, aber da musst du dich mit den windows-api funktionen rumschlagen. - einen performance-unterschied zwischen .exe und der .py gibt es nicht. wenn mans beschleunigen will sollte man auf psyco oder einen anderen python-interpreter zurückgreifen. python ist eine gute wahl für viele dinge
.
#3 2006-12-27 23:43 (Reply)
Hallo Marco, erstmal vielen Dank für dieses Tutorial. Es hat mir bei der Benutzung des Programmes sehr geholfen. Jetzt stehe ich inzwischen nur vor dem Problem, dass ich bei dem Erstellen einer --onefile auch gleich noch das icon von Tkinter mit integrieren will. In einem US-Forum habe ich dazu den Beitrag eines der Entwickler gelesen:"If you specify it as icon (--icon to Makespec), it means you want it as icon of the executable.(wie in deiner Beschreibung) If you just want to add it as an additional data file (genau das was ich bräuchte), read the manual about that." Tja, und da stand ich jetzt und habe "that" zusammen mit meinem Bruder fast 5 Stunden lang versucht im Manual die passende Option oder was auch immer, herauszufinden. Wenn du also einen Tipp hättest, wie ich das bewerkstelligen könnte, wäre ich dir sehr dankbar. Gruß Thomas
#3.1 2006-12-28 22:18 (Reply)
hi thomas. auf anhieb kann ich zwar nicht helfen, aber vielleicht schaust du dir mal die .spec datei mit einem editor an. vielleicht kannst du ohne die option leben, in dem du die datei selbst änderst um das icon einzubinden.
#4 2007-02-28 11:27 (Reply)
Hallo Marco, schönes Tutorial. Ich suche nach einer Möglichkeit aus Python Code eine "normale" Windows DLL zu erstellen, mit PyInstaller hab ich bisher nur COM-DLL´s erstellen können. Gibt es dazu auch eine Möglichkeit? Ciao Chris
#5 2007-12-15 14:13 (Reply)
Hallo, danke für die ausführliche Beschreibung! Ich habe lange danach gesucht, wollte meine Scripte in EXE-Files umwandeln
Bei mir gibt es allerdings schon im ersten Schritt, nämlich dem ausführen der Configure.py Datei einen Fehler! Ich habe es nicht per Eingabeaufforderung wie du gemacht, sondern einfach Configure.py geöffnet und ausgeführt, dabei gab es drei Fehler, die mit einem "W" anstatt einem "I" begannen...
Das ist der Text, der rauskam(siehe zum Fehler Zeile 2 f.)
I: computing EXE_dependencies
W: Cannot determine your Windows or System directories
W: Please add them to your PATH if .dlls are not found
W: or install starship.python.net/skippy/win32/Downloads.html
I: Finding TCL/TK...
I: found TCL/TK version 8.4
I: testing for Zlib...
I: ... Zlib available
I: Testing for ability to set icons, version resources...
I: ... resource update unavailable - No module named win32api
I: Testing for Unicode support...
I: ... Unicode available
I: testing for UPX...
I: ...UPX unavailable
I: computing PYZ dependencies...
Weißt du, was das für ein Fehler sein könnte?
Gruß Markus
#5.1 2007-12-15 14:21 (Reply)
Hast du denn die Win32 extensions installiert? Die werden benötigt, um die entsprechenden dlls zu laden/finden.
#6 2007-12-15 23:15 (Reply)
Nein^^ Wusste nicht, dass man das instalieren muss *peinlich* Hat jemand einen passenden Link, da ich nicht weiß, welche Extrensions das genau sind? Das wäre super!
Gruß Markus
#6.1 2007-12-16 12:42 (Reply)
steht sogar in der fehlermeldung mit drin
http://starship.python.net/skippy/win32/Downloads.html
#7 2008-02-28 16:59 (Reply)
Hallo nochmal, ich habe versucht das Icon meines Programmes einzufügen und es hat im IDLE auch funktioniert, aber wenn ich es zum Exe-file mache, dann startet das ganze Programm erst gar nicht... Lösche ich die Zeile mit dem Icon-Ersetzen, dann klappt es wieder -.- Also definitiv hängt das mit den Icons zusammen. Die fehlermeldungen, die in der Warn stehen sind diese hier: W: no module named posix (conditional import by os) W: no module named optik.__all__ (top-level import by optparse) W: no module named fcntl (top-level import by tempfile) W: no module named readline (delayed, conditional import by cmd) W: no module named readline (delayed import by pdb) W: no module named pwd (delayed, conditional import by posixpath) W: no module named MacOS (top-level import by Tkinter) W: no module named org (top-level import by pickle) W: no module named _imaging_gif (top-level import by GifImagePlugin) W: no module named posix (delayed, conditional import by iu) W: no module named fcntl (conditional import by subprocess) W: no module named org (top-level import by copy) W: no module named _emx_link (conditional import by os) W: no module named optik.__version__ (top-level import by optparse) W: no module named Carbon (conditional import by tempfile) W: __all__ is built strangely at line 0 - __future__ (C:\Programme\Python\lib\__future__.pyc) W: delayed conditional __import__ hack detected at line 0 - doctest (C:\Programme\Python\lib\doctest.pyc) W: delayed exec statement detected at line 0 - doctest (C:\Programme\Python\lib\doctest.pyc) W: delayed __import__ hack detected at line 0 - encodings (C:\Programme\Python\lib\encodings\__init__.pyc) W: delayed conditional __import__ hack detected at line 0 - Image (C:\Programme\Python\lib\site-packages\PIL\Image.pyc) W: delayed exec statement detected at line 0 - Tkinter (C:\Programme\Python\lib\lib-tk\Tkinter.pyc) W: __all__ is built strangely at line 0 - optparse (C:\Programme\Python\PyInstaller-1.3\optparse.pyc) W: __all__ is built strangely at line 0 - dis (C:\Programme\Python\lib\dis.pyc) W: delayed eval hack detected at line 0 - os (C:\Programme\Python\lib\os.pyc) W: delayed conditional __import__ hack detected at line 0 - unittest (C:\Programme\Python\lib\unittest.pyc) W: delayed conditional __import__ hack detected at line 0 - unittest (C:\Programme\Python\lib\unittest.pyc) W: __all__ is built strangely at line 0 - tokenize (C:\Programme\Python\lib\tokenize.pyc) W: delayed conditional exec statement detected at line 0 - iu (C:\Programme\Python\PyInstaller-1.3\iu.pyc) W: delayed conditional exec statement detected at line 0 - iu (C:\Programme\Python\PyInstaller-1.3\iu.pyc) W: delayed exec statement detected at line 0 - bdb (C:\Programme\Python\lib\bdb.pyc) W: delayed eval hack detected at line 0 - bdb (C:\Programme\Python\lib\bdb.pyc) W: delayed eval hack detected at line 0 - bdb (C:\Programme\Python\lib\bdb.pyc) W: delayed __import__ hack detected at line 0 - pickle (C:\Programme\Python\lib\pickle.pyc) W: delayed __import__ hack detected at line 0 - pickle (C:\Programme\Python\lib\pickle.pyc) W: delayed eval hack detected at line 0 - gettext (C:\Programme\Python\lib\gettext.pyc) W: delayed __import__ hack detected at line 0 - optik.option_parser (C:\Programme\Python\PyInstaller-1.3\optik\option_parser.pyc) W: delayed conditional eval hack detected at line 0 - warnings (C:\Programme\Python\lib\warnings.pyc) W: delayed conditional __import__ hack detected at line 0 - warnings (C:\Programme\Python\lib\warnings.pyc) W: __all__ is built strangely at line 0 - optik (C:\Programme\Python\PyInstaller-1.3\optik\__init__.pyc) W: delayed exec statement detected at line 0 - pdb (C:\Programme\Python\lib\pdb.pyc) W: delayed conditional eval hack detected at line 0 - pdb (C:\Programme\Python\lib\pdb.pyc) W: delayed eval hack detected at line 0 - pdb (C:\Programme\Python\lib\pdb.pyc) W: delayed conditional eval hack detected at line 0 - pdb (C:\Programme\Python\lib\pdb.pyc) W: delayed eval hack detected at line 0 - pdb (C:\Programme\Python\lib\pdb.pyc) Hat jemand eine Ahnung, was die bedeuten und wie ich es zum Laufen bekomme?! Nebenbei funzt auch nicht die Funktion dem Exefile ein Icon zuzuweisen als Bild... (per --icon=FILE.ICO) Und warum kann man eigentlich überhaupt kein Programm schreiben, bei dem es keinen Fehler gibt, und in der warn.txt. einfach mal nichts steht? Selbst bei einem hello-world programm gibt es fehler, was ich nicht verstehe... Viele Grüße Markus