Nuitka-Einführung

Der bessere Weg, Python zu kompilieren

29.09.2022
Von 
Serdar Yegulalp schreibt für unsere US-Schwesterpublikation Infoworld.
Mit Nuitka können Sie Python-Apps zu Standard-Executables kompilieren und sie ohne Laufzeitumgebung distribuieren. So geht's.
Python-Anwendungen kompilieren und redistribuieren? Nuitka unterstützt Sie dabei mit einem radikalen Ansatz.
Python-Anwendungen kompilieren und redistribuieren? Nuitka unterstützt Sie dabei mit einem radikalen Ansatz.
Foto: Casimiro PT - shutterstock.com

Während die Popularität von Python zunimmt, treten auch seine Limitationen immer deutlicher zutage: Python-Anwendungen zu schreiben und sie an Personen zu distribuieren, die Python nicht installiert haben, kann ein schwieriges Unterfangen darstellen. Der gängigste Weg, um dieses Problem zu umgehen: Das Programm zusammen mit allen unterstützenden Bibliotheken und Dateien sowie der Python-Laufzeitumgebung zu verpacken. Dafür stehen Tools wie PyInstaller bereit - diese erfordern allerdings jede Menge Kadenz, um korrekt zu funktionieren. Davon abgesehen ist es oft auch möglich, den Quellcode einer Python-Anwendung aus dem resultierenden Paket zu extrahieren. Das wird in einigen Fällen zum Ausschlusskriterium.

Das Third-Party-Projekt nuitka bietet an dieser Stelle eine radikale Lösung an: Es kompiliert ein Python-Programm in eine C-Binärdatei - und zwar nicht, indem es die CPython-Laufzeitumgebung mit dem Bytecode des Programms verpackt, sondern indem es Python-Anweisungen in C übersetzt. Das Ergebnis kann in einer .zip-Datei verteilt oder zusammen mit einem anderen Drittanbieterprodukt in ein Installationsprogramm gepackt werden.

Dabei versucht Nuitka auch, maximale Kompatibilität mit dem Python-Ökosystem aufrechtzuerhalten und die Performance von kompilierten Python-Anwendungen zu verbesssern. Ersteres soll gewährleisten, dass Bibliotheken von Drittanbietern wie NumPy zuverlässig funktionieren. Was die Performance-Zuwächse angeht, gibt es keine Garantien - die Ausbeute variiert stark zwischen den einzelnen Workloads. Im Allgemeinen empfiehlt es sich, Nuitka nicht in erster Linie einzusetzen um die Leistung zu verbessern - vielmehr ist Nuitka als Bündelungslösung zu verstehen.

Nuitka installieren

Nuitka funktioniert mit den Python-Versionen 2.6 bis 2.7 und 3.3 bis 3.10 und kann Binärdateien für

  • Microsoft Windows,

  • macOS,

  • Linux und

  • FreeBSD/NetBSD kompilieren.

Dabei sollten Sie beachten, dass eine Cross-Kompilierung nicht möglich ist. Sie müssen also die Binärdateien auf der Zielplattform kompilieren. Für jede Plattform benötigen Sie neben der Python-Laufzeitumgebung, auch einen C-Compiler. Unter Microsoft Windows wird Visual Studio 2022 oder höher empfohlen, aber es ist auch möglich, MinGW-w64 C11 (gcc 11.2 oder höher) zu verwenden. Für andere Plattformen können Sie auf gcc 5.1 oder höher, g++ 4.4 oder höher, clang oder clang-cl auf Windows unter Visual Studio zurückgreifen.

Beachten Sie außerdem: Sie benötigen Python 2.7, wenn Sie Python 3.3 oder Python 3.4 verwenden (die seit langem veraltet sind), weil Abhängigkeiten zwischen den Tools bestehen. Das sollte auch ein Argument dafür sein, wenn möglich die neueste Python-Version zu verwenden.

Optimal ist es, Nuitka in einer virtuellen Umgebung zusammen mit Ihrem Projekt als Entwicklungsabhängigkeit und nicht als Distributionsabhängigkeit zu installieren. Nuitka selbst wird nicht mit Ihrem Projekt gebündelt oder von diesem verwendet, es führt lediglich die Bündelung durch.

Nuitka nutzen

Sobald Sie Nuitka installiert haben, verwenden Sie nuitka oder python -m nuitka, um zu beginnen. Das Erste, was Sie mit Nuitka tun sollten: Überprüfen ob die gesamte Toolchain funktioniert - einschließlich Ihres C-Compilers. Um das zu testen, kompilieren Sie ein einfaches "Hello world"-Python-Programm - nennen wir es main.py:

print ("Hello world")

Wenn Sie ein Python-Programm mit Nuitka kompilieren, übergeben Sie den Namen des Einstiegsmoduls als Parameter an Nuitka, zum Beispiel nuitka main.py. Wenn Nuitka auf diese Weise aufgerufen wird, nimmt Nuitka main.py auf und erstellt daraus eine einzige ausführbare Datei.

In diesem Beispiel kompiliert Nuitka nur diese eine Python-Datei zu einem Executable, da wir nur die Funktionalität testen wollen. Es wird weder etwas anderes kompilieren, noch wird es etwas für die Weitergabe bündeln. Diese eine Datei zu kompilieren, sollte jedoch ausreichen, um festzustellen, ob Nuitkas Toolchain korrekt eingerichtet ist. Sobald die Kompilierung abgeschlossen ist, sollten Sie eine ausführbare Binärdatei sehen, die im selben Verzeichnis wie das Python-Programm liegt. Führen Sie diese Datei aus, um sicherzustellen, dass sie funktioniert. Sie können Ihre mit Nuitka kompilierte App auch automatisch ausführen lassen, indem Sie --run als Kommandozeilen-Flag nutzen.

Ihre erste Testkompilierung mit Nuitka wird wahrscheinlich in wenigen Sekunden abgeschlossen sein. Lassen Sie sich davon nicht beirren. Im Moment kompilieren Sie schließlich nur ein einzelnes Modul, nicht Ihr ganzes Programm. Die Kompilierung einer vollständigen Anwendung mit Nuitka kann einige Minuten (oder länger) in Anspruch nehmen, je nachdem, wie viele Module das Programm verwendet.

Python-Programme mit Nuitka kompilieren

Standardmäßig kompiliert Nuitka nur das von Ihnen angegebene Modul. Wenn Ihr Modul Importe enthält - sei es aus anderen Teilen Ihres Programms, aus der Standardbibliothek oder aus Paketen von Drittanbietern - müssen Sie diese ebenfalls kompilieren.

Beispielhaft betrachten wir eine modifizierte Version des "Hello world"-Programms mit einem angrenzenden Modul namens greet.py:

def greet(name):

print ("Hallo ", name)

und ein modifiziertes main.py:

import greet

greet.greet("world")

Um beide Module zu kompilieren, können Sie --follow-imports verwenden:

nuitka --follow-imports main.py

Das sorgt dafür, dass alle im Programm benötigten Importe von den import-Anweisungen verfolgt und zusammen kompiliert werden.

Mit einer weiteren Option, --nofollow-import-to, können Sie bestimmte Unterverzeichnisse vom Importprozess ausschließen. Diese Option ist nützlich, um Testsuiten oder Module auszuschließen, von denen Sie wissen, dass sie nie verwendet werden. Sie ermöglicht auch die Angabe eines Platzhalters.

So sieht es aus, wenn Sie große und komplexe Anwendungen mit Nuitka kompilieren. In diesem Beispiel soll (neben anderen) auch das Pyglet-Modul in der Standardbibliothek kompiliert werden.
So sieht es aus, wenn Sie große und komplexe Anwendungen mit Nuitka kompilieren. In diesem Beispiel soll (neben anderen) auch das Pyglet-Modul in der Standardbibliothek kompiliert werden.

Dynamische Importe einbinden

Ein Problem, mit dem Python-Benutzer oft konfrontiert werden, wenn sie versuchen, eine Python-Anwendung zu verpacken, um sie zu redistribuieren. Die Option --follow-imports folgt nur Importen, die explizit im Code durch eine import-Anweisung deklariert sind. Dynamische Importe sind an dieser Stelle nicht inkludiert.

Um das zu umgehen, können Sie --include-plugin-directory verwenden, um einen oder mehrere Pfade zu Modulen anzugeben, die dynamisch importiert werden. Für ein Verzeichnis mit dem Namen mods, das dynamisch importierten Code enthält, würden Sie zum Beispiel Folgendes verwenden:

nuitka --follow-imports --include-plugin-directory=mods main.py

Dateien und Verzeichnisse einbeziehen

Wenn Ihr Python-Programm Dateien verwendet, die zur Laufzeit geladen werden, kann Nuitka auch diese nicht automatisch erkennen. Um einzelne Dateien und Verzeichnisse in ein von Nuitka gepacktes Programm einzubinden, verwenden Sie: --include-data-files und --include-data-dir.

Mit --include-data-files können Sie einen Platzhalter für die zu kopierenden Dateien angeben - und wohin sie kopiert werden sollen. Mit --include-data-files=/data/*=data/ werden beispielsweise alle Dateien, die mit dem Platzhalter data/* übereinstimmen, nach data/ in Ihrem Distributionsverzeichnis kopiert.

--include-data-dir funktioniert in etwa auf die gleiche Weise, nur dass hier kein Platzhalter verwendet wird, sondern nur ein Pfad zum Kopieren und ein Ziel im Distributionsverzeichnis angegeben werden kann. Ein Beispiel: --include-data-dir=/path/to/data=data würde alles in /path/to/data in das entsprechende Verzeichnis data in Ihrem Distributionsverzeichnis kopieren.

Python-Pakete und -Module einbinden

Eine weitere Möglichkeit, Importe zu spezifizieren, ist es, einen Namespace im Python-Stil anstelle eines Dateipfads zu verwenden. Das bewerkstelligen Sie mit --include-package. Der folgende Befehl würde zum Beispiel mypackage einschließen, wo immer es sich auf der Festplatte befindet (vorausgesetzt, Python kann es finden):

nuitka --include-package=mypackage main.py

Wenn Pakete ihre eigenen Dateien benötigen, können Sie diese mit der Option --include-package-data inkludieren:

nuitka --include-package=mypackage --include-package-data=mypackage main.py

Dieser Befehl weist Nuitka an, alle Dateien im Paketverzeichnis zu übernehmen, die eigentlich kein Code sind.

Wenn Sie nur ein einzelnes Modul einschließen wollen, können Sie --include-module verwenden:

nuitka --include-module=mypackage.mymodule main.py

Dieser Befehl weist Nuitka an, nur mypackage.mymodule einzuschließen, aber nichts anderes.

Python-Programme kompilieren und distribuieren

Wenn Sie ein Python-Programm mit Nuitka für die Weiterverbreitung kompilieren möchten, können Sie --standalone verwenden - das nimmt Ihnen den größten Teil der Arbeit ab: Dieses Kommando folgt automatisch allen Importen und erzeugt einen dist-Ordner, der die kompilierte ausführbare Datei und alle benötigten Unterstützungsdateien enthält. Um das Programm zu redistribuieren, müssen Sie lediglich dieses Verzeichnis kopieren.

Allerdings sollten Sie nicht erwarten, dass ein mit --standalone kompiliertes Programm beim ersten Mal funktioniert. Die allgemeine Dynamik von Python-Programmen garantiert, dass Sie einige der anderen oben beschriebenen Optionen verwenden müssen, um sicherzustellen, dass das kompilierte Programm richtig läuft. Wenn Sie zum Beispiel eine GUI-Anwendung haben, die bestimmte Schriftarten benötigt, müssen Sie diese möglicherweise mit --include-data-files oder --include-data-dir in Ihre Distribution einfügen.

Wie bereits erwähnt, kann die Kompilierungszeit für eine --standalone-Anwendung deutlich länger ausfallen als für eine Testkompilierung. Planen Sie die benötigte Zeit ein, um eine vollständig kompilierte Applikation zu testen, sobald Sie eine Vorstellung davon haben, wie lange das dauern wird.

Schließlich bietet Nuitka eine weitere Build-Option: --onefile. Diejenigen, die mit PyInstaller vertraut sind, werden sich wie zuhause fühlen: Es komprimiert Ihre gesamte Anwendung, einschließlich aller abhängigen Dateien, in eine einzige ausführbare Datei, ohne dass weitere Dateien für die Redistribuierung benötigt werden. Dabei ist es jedoch wichtig zu wissen, dass --onefile unter Linux und Microsoft Windows unterschiedlich funktioniert:

  • Unter Linux wird ein virtuelles Dateisystem mit dem Inhalt des Archivs gemountet.

  • Unter Windows werden die Dateien in ein temporäres Verzeichnis entpackt und von dort aus ausgeführt - und zwar bei jedem Start der Anwendung. --onefile unter Windows zu verwenden, kann die Zeit bis zum Programmstart erheblich verlängern.

Dieser Beitrag basiert auf einem Artikel unserer US-Schwesterpublikation InfoWorld. (fm)