Warum gibt es so viele Pythons? Ein Python-Implementierungsvergleich
Veröffentlicht: 2022-03-11Python ist erstaunlich.
Überraschenderweise ist das eine ziemlich zweideutige Aussage. Was meine ich mit „Python“? Meine ich Python die abstrakte Schnittstelle ? Meine ich CPython, die gängige Python- Implementierung (und nicht zu verwechseln mit dem gleichnamigen Cython)? Oder meine ich etwas ganz anderes? Vielleicht beziehe ich mich indirekt auf Jython oder IronPython oder PyPy. Oder vielleicht bin ich wirklich ins kalte Wasser gesprungen und spreche von RPython oder RubyPython (was sehr, sehr unterschiedliche Dinge sind).
Während die oben genannten Technologien allgemein benannt und allgemein referenziert werden, dienen einige von ihnen völlig unterschiedlichen Zwecken (oder arbeiten zumindest auf völlig unterschiedliche Weise).
Während meiner Arbeit mit den Python-Schnittstellen bin ich auf unzählige dieser .*ython-Tools gestoßen. Aber erst vor kurzem habe ich mir die Zeit genommen, zu verstehen, was sie sind, wie sie funktionieren und warum sie (auf ihre eigene Weise) notwendig sind.
In diesem Tutorial beginne ich bei Null und gehe durch die verschiedenen Python-Implementierungen, um mit einer gründlichen Einführung in PyPy zu schließen, von dem ich glaube, dass es die Zukunft der Sprache ist.
Alles beginnt damit, zu verstehen, was „Python“ eigentlich ist.
Wenn Sie ein gutes Verständnis für Maschinencode, virtuelle Maschinen und dergleichen haben, können Sie gerne weitermachen.
„Wird Python interpretiert oder kompiliert?“
Dies ist ein häufiger Verwirrungspunkt für Python-Anfänger.
Das erste, was Sie bei einem Vergleich erkennen sollten, ist, dass 'Python' eine Schnittstelle ist. Es gibt eine Spezifikation, was Python tun und wie es sich verhalten soll (wie bei jeder Schnittstelle). Und es gibt mehrere Implementierungen (wie bei jeder Schnittstelle).
Als Zweites gilt es zu erkennen, dass „interpretiert“ und „kompiliert“ Eigenschaften einer Implementierung und nicht einer Schnittstelle sind.
Die Frage selbst ist also nicht wirklich wohlgeformt.
Das heißt, für die gängigste Python-Implementierung (CPython: in C geschrieben, oft einfach als „Python“ bezeichnet und sicherlich das, was Sie verwenden, wenn Sie nicht wissen, wovon ich spreche), lautet die Antwort: interpretiert , mit etwas Zusammenstellung. CPython kompiliert * Python-Quellcode in Bytecode und interpretiert dann diesen Bytecode und führt ihn so aus, wie er läuft.
* Hinweis: Dies ist keine 'Zusammenstellung' im herkömmlichen Sinne des Wortes. Typischerweise würden wir sagen, dass „Kompilieren“ eine Hochsprache nimmt und sie in Maschinencode umwandelt. Aber es ist eine Art "Zusammenstellung".
Schauen wir uns diese Antwort genauer an, da sie uns hilft, einige der Konzepte zu verstehen, die später in diesem Beitrag auftauchen.
Bytecode vs. Maschinencode
Es ist sehr wichtig, den Unterschied zwischen Bytecode und Maschinencode (auch bekannt als nativer Code) zu verstehen, der vielleicht am besten durch ein Beispiel veranschaulicht wird:
- C wird in Maschinencode kompiliert, der dann direkt auf Ihrem Prozessor ausgeführt wird. Jede Anweisung weist Ihre CPU an, Dinge zu bewegen.
- Java wird in Bytecode kompiliert, der dann auf der Java Virtual Machine (JVM) ausgeführt wird, einer Abstraktion eines Computers, der Programme ausführt. Jede Anweisung wird dann von der JVM verarbeitet, die mit Ihrem Computer interagiert.
Kurz gesagt: Maschinencode ist viel schneller, aber Bytecode ist portabler und sicherer .
Maschinencode sieht je nach Maschine anders aus, aber Bytecode sieht auf allen Maschinen gleich aus. Man könnte sagen, dass Maschinencode für Ihr Setup optimiert ist.
Zurück zur CPython-Implementierung sieht der Toolchain-Prozess wie folgt aus:
- CPython kompiliert Ihren Python-Quellcode in Bytecode.
- Dieser Bytecode wird dann auf der CPython Virtual Machine ausgeführt.
Alternative VMs: Jython, IronPython und mehr
Wie ich bereits erwähnt habe, hat Python mehrere Implementierungen. Wie bereits erwähnt, ist CPython am gebräuchlichsten, aber es gibt andere, die für diesen Vergleichsleitfaden erwähnt werden sollten. Dies ist eine in C geschriebene Python-Implementierung, die als „Standard“-Implementierung betrachtet wird.
Aber was ist mit den alternativen Python-Implementierungen? Eines der bekanntesten ist Jython, eine in Java geschriebene Python-Implementierung, die die JVM verwendet. Während CPython Bytecode zur Ausführung auf der CPython-VM erzeugt, erzeugt Jython Java-Bytecode zur Ausführung auf der JVM (dies ist das gleiche Zeug, das beim Kompilieren eines Java-Programms erzeugt wird).
„Warum sollten Sie jemals eine alternative Implementierung verwenden?“, fragen Sie sich vielleicht. Nun, zum einen spielen diese verschiedenen Python-Implementierungen gut mit verschiedenen Technologie-Stacks zusammen .
CPython macht es sehr einfach, C-Erweiterungen für Ihren Python-Code zu schreiben, da es am Ende von einem C-Interpreter ausgeführt wird. Jython hingegen macht es sehr einfach, mit anderen Java-Programmen zu arbeiten: Sie können beliebige Java-Klassen ohne zusätzlichen Aufwand importieren und Ihre Java-Klassen aus Ihren Jython-Programmen aufrufen und verwenden. (Außerdem: Wenn Sie nicht genau darüber nachgedacht haben, ist das eigentlich verrückt. Wir sind an dem Punkt, an dem Sie verschiedene Sprachen mischen und zerdrücken und sie alle auf dieselbe Substanz herunterkompilieren können. (Wie von Rostin erwähnt, programmiert das Fortran und C-Code zu mischen gibt es schon eine Weile. Also ist das natürlich nicht unbedingt neu. Aber es ist immer noch cool.))
Als Beispiel ist dies ein gültiger Jython-Code:
[Java HotSpot(TM) 64-Bit Server VM (Apple Inc.)] on java1.6.0_51 >>> from java.util import HashSet >>> s = HashSet(5) >>> s.add("Foo") >>> s.add("Bar") >>> s [Foo, Bar]
IronPython ist eine weitere beliebte Python-Implementierung, die vollständig in C# geschrieben ist und auf den .NET-Stack abzielt. Insbesondere läuft es auf der sogenannten .NET Virtual Machine, der Common Language Runtime (CLR) von Microsoft, vergleichbar mit der JVM.
Man könnte sagen, dass Jython : Java :: IronPython : C# . Sie laufen auf denselben jeweiligen VMs, Sie können C#-Klassen aus Ihrem IronPython-Code und Java-Klassen aus Ihrem Jython-Code usw. importieren.
Es ist durchaus möglich, zu überleben, ohne jemals eine Nicht-CPython-Python-Implementierung zu berühren. Der Wechsel bietet jedoch Vorteile, von denen die meisten von Ihrem Technologie-Stack abhängen. Verwenden Sie viele JVM-basierte Sprachen? Jython könnte etwas für Sie sein. Alles über den .NET-Stack? Vielleicht sollten Sie IronPython ausprobieren (und vielleicht haben Sie es bereits).
Übrigens: Obwohl dies kein Grund wäre, eine andere Implementierung zu verwenden, beachten Sie, dass sich diese Implementierungen tatsächlich im Verhalten unterscheiden, abgesehen davon, wie sie Ihren Python-Quellcode behandeln. Diese Unterschiede sind jedoch in der Regel geringfügig und lösen sich auf oder treten im Laufe der Zeit auf, da sich diese Implementierungen in der aktiven Entwicklung befinden. Beispielsweise verwendet IronPython standardmäßig Unicode-Strings; CPython verwendet jedoch standardmäßig ASCII für die Versionen 2.x (schlägt mit einem UnicodeEncodeError für Nicht-ASCII-Zeichen fehl), unterstützt jedoch standardmäßig Unicode-Zeichenfolgen für 3.x.
Just-in-Time-Kompilierung: PyPy und die Zukunft
Wir haben also eine Python-Implementierung in C geschrieben, eine in Java und eine in C#. Der nächste logische Schritt: eine Python-Implementierung, geschrieben in … Python. (Der gebildete Leser wird feststellen, dass dies leicht irreführend ist.)
Hier könnte es verwirrend werden. Lassen Sie uns zunächst die Just-in-Time-Kompilierung (JIT) besprechen.
JIT: Das Warum und Wie
Denken Sie daran, dass nativer Maschinencode viel schneller ist als Bytecode. Nun, was wäre, wenn wir einen Teil unseres Bytecodes kompilieren und ihn dann als nativen Code ausführen könnten? Wir müssten einen gewissen Preis zahlen, um den Bytecode zu kompilieren (dh Zeit), aber wenn das Endergebnis schneller wäre, wäre das großartig! Dies ist die Motivation der JIT-Kompilierung, einer hybriden Technik, die die Vorteile von Interpretern und Compilern kombiniert. Grundsätzlich möchte JIT die Kompilierung verwenden, um ein interpretiertes System zu beschleunigen.
Ein gängiger Ansatz von JITs ist beispielsweise:

- Identifizieren Sie Bytecode, der häufig ausgeführt wird.
- Kompilieren Sie es in nativen Maschinencode.
- Zwischenspeichern Sie das Ergebnis.
- Wann immer derselbe Bytecode ausgeführt werden soll, greifen Sie stattdessen auf den vorkompilierten Maschinencode zurück und profitieren Sie von den Vorteilen (z. B. Geschwindigkeitssteigerungen).
Darum geht es bei der PyPy-Implementierung: JIT nach Python bringen (siehe Anhang für frühere Bemühungen). Es gibt natürlich noch andere Ziele: PyPy soll plattformübergreifend, speicherarm und stapellos unterstützend sein. Aber JIT ist wirklich sein Verkaufsargument. Als Durchschnitt über eine Reihe von Zeittests soll die Leistung um den Faktor 6,27 verbessert werden. Eine Aufschlüsselung finden Sie in diesem Diagramm aus dem PyPy Speed Center:
PyPy ist schwer zu verstehen
PyPy hat ein enormes Potenzial und ist derzeit sehr kompatibel mit CPython (so dass es Flask, Django usw. ausführen kann).
Aber es gibt viel Verwirrung um PyPy (siehe zum Beispiel diesen unsinnigen Vorschlag, ein PyPyPy zu erstellen …). Meiner Meinung nach liegt das in erster Linie daran, dass PyPy eigentlich zwei Dinge sind:
Ein in RPython geschriebener Python-Interpreter (nicht Python (ich habe vorher gelogen)). RPython ist eine Teilmenge von Python mit statischer Typisierung. In Python ist es „meistens unmöglich“, rigoros über Typen nachzudenken (Warum ist es so schwer? Bedenken Sie die Tatsache, dass:
x = random.choice([1, "foo"])
wäre gültiger Python-Code (Dank an Ademan). Was ist der Typ von
x
? Wie können wir über Typen von Variablen argumentieren, wenn die Typen nicht einmal streng durchgesetzt werden?). Mit RPython opfern Sie etwas Flexibilität, machen es aber stattdessen viel, viel einfacher, über die Speicherverwaltung und so weiter nachzudenken, was Optimierungen ermöglicht.Ein Compiler, der RPython-Code für verschiedene Ziele kompiliert und in JIT hinzufügt. Die Standardplattform ist C, dh ein RPython-zu-C-Compiler, aber Sie können auch die JVM und andere als Ziel verwenden.
Nur aus Gründen der Klarheit in diesem Python-Vergleichsleitfaden werde ich diese als PyPy (1) und PyPy (2) bezeichnen.
Warum brauchen Sie diese beiden Dinge und warum unter einem Dach? Stellen Sie sich das so vor: PyPy (1) ist ein in RPython geschriebener Interpreter. Es nimmt also den Python-Code des Benutzers auf und kompiliert ihn zu Bytecode. Aber der Interpreter selbst (in RPython geschrieben) muss von einer anderen Python-Implementierung interpretiert werden, um ausgeführt zu werden, richtig?
Nun, wir könnten einfach CPython verwenden, um den Interpreter auszuführen. Aber das wäre nicht sehr schnell.
Stattdessen ist die Idee, dass wir PyPy (2) (als RPython Toolchain bezeichnet) verwenden, um den Interpreter von PyPy in Code für eine andere Plattform (z. B. C, JVM oder CLI) herunterzukompilieren, die auf unserem Computer ausgeführt wird, und JIT als hinzufügen Gut. Es ist magisch: PyPy fügt dynamisch JIT zu einem Interpreter hinzu und generiert seinen eigenen Compiler! ( Auch das ist verrückt: Wir kompilieren einen Interpreter und fügen einen weiteren separaten, eigenständigen Compiler hinzu. )
Am Ende ist das Ergebnis eine eigenständige ausführbare Datei, die den Python-Quellcode interpretiert und JIT-Optimierungen ausnutzt. Genau das wollten wir! Es ist ein Bissen, aber vielleicht hilft dieses Diagramm:
Um es noch einmal zu wiederholen, die wahre Schönheit von PyPy besteht darin, dass wir uns selbst eine Reihe verschiedener Python-Interpreter in RPython schreiben konnten, ohne uns Gedanken über JIT machen zu müssen. PyPy würde dann JIT für uns mit der RPython Toolchain/PyPy (2) implementieren .
Wenn wir noch abstrakter werden, könnten Sie theoretisch einen Interpreter für jede Sprache schreiben, ihn an PyPy füttern und ein JIT für diese Sprache erhalten. Dies liegt daran, dass sich PyPy auf die Optimierung des eigentlichen Interpreters konzentriert und nicht auf die Details der Sprache, die es interpretiert.
Als kleinen Exkurs möchte ich erwähnen, dass das JIT selbst absolut faszinierend ist. Es verwendet eine Technik namens Tracing, die wie folgt ausgeführt wird:
- Führen Sie den Interpreter aus und interpretieren Sie alles (fügen Sie kein JIT hinzu).
- Führen Sie ein leichtes Profiling des interpretierten Codes durch.
- Identifizieren Sie Vorgänge, die Sie zuvor ausgeführt haben.
- Kompilieren Sie diese Codebits in Maschinencode.
Darüber hinaus ist dieses Papier sehr zugänglich und sehr interessant.
Zum Abschluss: Wir verwenden den RPython-zu-C-Compiler (oder eine andere Zielplattform) von PyPy, um den RPython-implementierten Interpreter von PyPy zu kompilieren.
Einpacken
Nach einem längeren Vergleich von Python-Implementierungen muss ich mich fragen: Warum ist das so toll? Warum lohnt es sich, diese verrückte Idee weiterzuverfolgen? Ich denke, Alex Gaynor hat es in seinem Blog gut formuliert: „[PyPy ist die Zukunft], weil [es] eine bessere Geschwindigkeit, mehr Flexibilität und eine bessere Plattform für das Wachstum von Python bietet.“
Zusamenfassend:
- Es ist schnell, weil es Quellcode in nativen Code kompiliert (mithilfe von JIT).
- Es ist flexibel, weil es den JIT mit sehr wenig zusätzlichem Aufwand zu Ihrem Interpreter hinzufügt .
- Es ist (wieder) flexibel, weil Sie Ihre Interpreter in RPython schreiben können , das einfacher zu erweitern ist als beispielsweise C (tatsächlich ist es so einfach, dass es ein Tutorial zum Schreiben Ihrer eigenen Interpreter gibt).
Anhang: Andere Python-Namen, die Sie vielleicht schon gehört haben
Python 3000 (Py3k): ein alternativer Name für Python 3.0, eine große, abwärtsinkompatible Python-Version, die 2008 auf den Markt kam. Das Py3k-Team sagte voraus, dass es etwa fünf Jahre dauern würde, bis diese neue Version vollständig angenommen wird. Und während die meisten (Warnung: anekdotische Behauptung) Python-Entwickler weiterhin Python 2.x verwenden, sind sich die Leute zunehmend Py3k bewusst.
- Cython: eine Obermenge von Python, die Bindungen zum Aufrufen von C-Funktionen enthält.
- Ziel: Sie dürfen C-Erweiterungen für Ihren Python-Code schreiben.
- Außerdem können Sie Ihrem vorhandenen Python-Code statische Typisierung hinzufügen, sodass er kompiliert werden kann und eine C-ähnliche Leistung erreicht.
- Dies ist ähnlich wie PyPy, aber nicht dasselbe. In diesem Fall erzwingen Sie die Eingabe des Codes des Benutzers, bevor Sie ihn an einen Compiler übergeben. Mit PyPy schreiben Sie einfaches altes Python, und der Compiler übernimmt alle Optimierungen.
Numba: ein „just-in-time spezialisierter Compiler“, der JIT zu annotiertem Python-Code hinzufügt. Im Grunde geben Sie ihm einige Hinweise, und es beschleunigt Teile Ihres Codes. Numba ist Teil der Anaconda-Distribution, einer Reihe von Paketen zur Datenanalyse und -verwaltung.
IPython: ganz anders als alles andere besprochen. Eine Computerumgebung für Python. Interaktiv mit Unterstützung für GUI-Toolkits und Browsererfahrung usw.
- Psyco: ein Python-Erweiterungsmodul und eine der frühen Python-JIT-Bemühungen. Es wurde jedoch seitdem als „nicht gewartet und tot“ markiert. Tatsächlich arbeitet der Hauptentwickler von Psyco, Armin Rigo, jetzt an PyPy.
Python-Sprachbindungen
RubyPython: eine Brücke zwischen den Ruby- und Python-VMs. Ermöglicht das Einbetten von Python-Code in Ihren Ruby-Code. Sie definieren, wo Python startet und stoppt, und RubyPython marshallt die Daten zwischen den VMs.
PyObjc: Sprachbindungen zwischen Python und Objective-C, die als Brücke zwischen ihnen fungieren. Praktisch bedeutet dies, dass Sie Objective-C-Bibliotheken (einschließlich allem, was Sie zum Erstellen von OS X-Anwendungen benötigen) aus Ihrem Python-Code und Python-Module aus Ihrem Objective-C-Code verwenden können. In diesem Fall ist es praktisch, dass CPython in C geschrieben ist, was eine Teilmenge von Objective-C ist.
PyQt: Während PyObjc Ihnen eine Bindung für die OS X-GUI-Komponenten bietet, tut PyQt dasselbe für das Qt-Anwendungsframework, sodass Sie umfangreiche grafische Schnittstellen erstellen, auf SQL-Datenbanken zugreifen können usw. Ein weiteres Tool, das darauf abzielt, die Einfachheit von Python auf andere Frameworks zu übertragen.
JavaScript-Frameworks
pyjs (Pyjamas): ein Framework zum Erstellen von Web- und Desktop-Anwendungen in Python. Enthält einen Python-zu-JavaScript-Compiler, ein Widget-Set und einige weitere Tools.
Brython: eine in JavaScript geschriebene Python-VM, mit der Py3k-Code im Browser ausgeführt werden kann.