Heim >Backend-Entwicklung >Python-Tutorial >Ein Dolmetscher in einem Dolmetscher
Ein paar Monate nach Beginn der Entwicklung beschloss ich, dass mein Nordstern für Memphis darin bestehen würde, einen Flask-Server vollständig in meinem Interpreter zu betreiben. Ich hatte keine Ahnung, wie viel Arbeit das mit sich bringen würde, nur, dass es cool klang und mir auf dem Weg wahrscheinlich viel beibringen würde. Wenn ich dieses Ziel heute erreichen würde, würde ich mich vielleicht für FastAPI entscheiden oder gar nichts, weil das dumm von mir war.
Eine große Entscheidung, die ich treffen musste, war, wie ich mit der Python-Standardbibliothek umgehen sollte. Wie Sie wahrscheinlich wissen, ist die Standardbibliothek einer Sprache technisch gesehen nicht Teil der Sprachdefinition oder Laufzeit. Es ist in Veröffentlichungen enthalten, um die Sprache und Laufzeit nützlicher zu machen. Stellen Sie sich Python ohne Threading oder Async-Unterstützung vor. Sie könnten immer noch Ausdrücke auswerten und Klassen instanziieren, aber die meisten produktionsbereiten Programme benötigen eine Art Parallelitätsunterstützung.
Eine Möglichkeit wäre, die gesamte Standardbibliothek selbst neu zu schreiben. Ich baue einen Dolmetscher, nicht wahr? Ich glaube, dass dies der Ansatz von RustPython ist, ein bewundernswerter Weg. Ich dachte, ich hätte genug damit zu tun, die Laufzeit zum Laufen zu bringen, suchte nach allen möglichen Abstrichen und habe mich dagegen entschieden.
Die Python-Standardbibliothek besteht aus zwei Hauptteilen: den in Python implementierten Teilen und den in C implementierten Teilen. Praktischerweise hatte ich meinen eigenen Python-Interpreter. Könnte ich einfach die Python-Quelldatei vom Host-Rechner interpretieren, um Ersteres zu erfüllen? Ja, das könnte ich. Ich müsste jede verwendete Syntax und Funktion unterstützen, aber danach würde es einfach funktionieren.
Im C-Teil wird es interessant. Damals, im Jahr 2023, habe ich beschlossen, einen Python-Interpreter in meinen Python-Interpreter einzubetten, ohne vollständig zu verstehen, was das bedeutet. Jetzt war es an der Zeit, mich damit auseinanderzusetzen und zu entscheiden, ob ich bei diesem Ansatz bleiben oder einen anderen Weg wählen wollte.
Der Interop-Shop für Rust und Python ist Pyo3. Als einziges Spiel in der Stadt nutzt Pyo3 das Foreign Function Interface (FFI), damit Ihr Rust-Code Aufrufe in die CPython-Binärdatei tätigen kann. Dies funktioniert durch die Einigung auf das Application Binary Interface (ABI), ein Konzept, das ich während meiner Karriere bei AMD verwendet habe. Kernsoftware ftw!
Mein erster Anwendungsfall bestand darin, import sys auszuführen und mir dadurch ein Objekt zu geben, für das ich einen Mitgliedszugriffsvorgang durchführen konnte. Ich beginne hier mit der Dolmetschersprache, aber das ist die Art von REPL-Sitzung, von der ich spreche.
Python 3.12.5 (main, Aug 6 2024, 19:08:49) [Clang 15.0.0 (clang-1500.3.9.4)] Type "help", "copyright", "credits" or "license" for more information. >>> import sys >>> sys <module 'sys' (built-in)> >>> type(sys.modules) <class 'dict'>
Diese Funktionalität mit Pyo3 zu erhalten war unkompliziert.
pub struct CPythonModule(PyObject); impl CPythonModule { pub fn new(name: &str) -> Self { pyo3::prepare_freethreaded_python(); let pymodule = Python::with_gil(|py| PyModule::import(py, name).expect("Failed to import module").into() ); Self(pymodule) } }
Und wir können dies nutzen, um eine ähnliche REPL-Sitzung in Memphis durchzuführen, vorausgesetzt, Sie erinnern sich an den Cocktail an Funktionsflags, um dies zum Laufen zu bringen.
Python 3.12.5 (main, Aug 6 2024, 19:08:49) [Clang 15.0.0 (clang-1500.3.9.4)] Type "help", "copyright", "credits" or "license" for more information. >>> import sys >>> sys <module 'sys' (built-in)> >>> type(sys.modules) <class 'dict'>
Wenn Sie sich fragen: Könnten Sie nicht einfach diesen Ansatz verwenden, um die gesamte Standardbibliothek (einschließlich der in Python und C geschriebenen Teile) zu importieren und Ihr gesamtes Leben, Ihre Freiheit und das Streben nach Glück einfacher zu machen? Die Antwort ist ja. Das wäre ein gültiger Ansatz! Allerdings würde das meinen Interpreter mehr zu einer Hülle um CPython machen, als mir lieb ist. Dies ist eine Lernübung, daher bin ich für willkürliche Entscheidungen. Für die Puristen da draußen, die sagen, dass das Laden irgendeines CPython-Teils in Memphis Memphis zu keinem echten Interpreter macht, würde ich nur sagen: Bitte zeigen Sie mir Ihren Interpreter.
Ich habe einen Schnelltest mit htop durchgeführt, indem ich import sys innerhalb einer REPL-Sitzung mit Memphis und CPython ausgeführt habe. Da dadurch auf Memphis die CPython-Bibliotheken in den Speicher geladen werden, erhöhte sich die RAM-Nutzung (Resident Set Size in htop) um etwa 5 MB. Zum Vergleich: Die Memphis REPL nach dem Laden des SYS-Moduls verbraucht etwa 9 MB RAM, während die Python REPL vor und nach dem Laden des SYS-Moduls etwa gleich viel RAM verbraucht. Ich bin mir sicher, dass dies kein Vergleich von Äpfeln zu Äpfeln ist, aber es zeigte mir zumindest, dass Memphis meinen Computer nicht langsam ersticken würde.
Die nächste Komplexität bei diesem Setup besteht darin, meine Memphis-Objektdarstellung in CPython-Darstellungen zu konvertieren und umgekehrt. Dies ist ein Work-in-Progress und meine Hauptanweisung war zunächst „Nicht scheitern“ und in jüngerer Zeit „Warnungen anzeigen, wenn Sie eine verlustbehaftete Konvertierung durchführen.“
Hier ist meine Konvertierung von einem PyObject, das die Objektdarstellung auf der Pyo3-Seite ist, in ein ExprResult, meine Memphis-Darstellung.
pub struct CPythonModule(PyObject); impl CPythonModule { pub fn new(name: &str) -> Self { pyo3::prepare_freethreaded_python(); let pymodule = Python::with_gil(|py| PyModule::import(py, name).expect("Failed to import module").into() ); Self(pymodule) } }
Und hier ist der umgekehrte Vergleich. Beachten Sie, dass wir für beides ein Python-Objekt übergeben müssen, das unseren Zugriff auf die CPython-GIL (globale Interpretersperre) steuert.
memphis 0.1.0 REPL (Type 'exit()' to quit) >>> import sys >>> sys <module 'sys' (built-in)> >>> type(sys.modules) <class 'dict' (built-in)>
Dies ist ein reichhaltiges Gebiet, das ich gerne weiter erforschen würde. Hier sind einige der Richtungen, die ich in Betracht gezogen habe:
Ich übertreibe es, weil ich im Moment kaum ein C-Modul laden kann, aber meine Neugier in diesem Bereich ist wirklich grenzenlos.
Ich beschäftige mich weiterhin damit, als ich auf einen neuen Konvertierungsfehler stoße, während ich mich auf den Weg mache, Flask zum Booten zu bringen. Diese Übung ist eine gute Erinnerung daran, dass alle Objekte (oder Klassen, Module usw.) eine Reihe von Attributen sind, die in einem bekannten Format im Speicher vorhanden sind. Wenn wir dieses Format gut genug verstehen, sollten wir in der Lage sein, unglaubliche Dinge zu tun, unabhängig davon, ob es sich um Memphis oder CPython handelt.
Diese Philosophie treibt auch meine Arbeit mit From Scratch Code an. Wenn Sie es satt haben, eine Bibliothek nicht in Ihrem Code zum Laufen zu bringen, empfehle ich Ihnen, einen Schritt zurückzutreten und zu fragen: Was macht die Bibliothek eigentlich? Benötigen Sie es oder könnte eine einfachere Lösung funktionieren? Ich glaube daran, diese Neugier auf Software zu wecken – und ich helfe Ihnen gerne dabei, diese Denkweise in Ihren Werkzeugkasten zu integrieren.
Wenn Sie weitere Beiträge dieser Art direkt in Ihrem Posteingang erhalten möchten, können Sie sich hier anmelden!
Neben der Betreuung von Software-Ingenieuren schreibe ich auch über meine Erfahrungen als Erwachsener, bei dem Autismus diagnostiziert wurde. Weniger Code und die gleiche Anzahl an Witzen.
Das obige ist der detaillierte Inhalt vonEin Dolmetscher in einem Dolmetscher. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!