PEP 432 – Umstrukturierung der CPython-Startsequenz
- Autor:
- Alyssa Coghlan <ncoghlan at gmail.com>, Victor Stinner <vstinner at python.org>, Eric Snow <ericsnowcurrently at gmail.com>
- Discussions-To:
- Capi-SIG Liste
- Status:
- Zurückgezogen
- Typ:
- Standards Track
- Benötigt:
- 587
- Erstellt:
- 28-Dez-2012
- Post-History:
- 28-Dez-2012, 02-Jan-2013, 30-Mrz-2019, 28-Jun-2020
Inhaltsverzeichnis
- Rücknahme eines PEP
- Zusammenfassung
- Vorschlag
- Hintergrund
- Wesentliche Anliegen
- Erforderliche Konfigurationseinstellungen
- Implementierungsstrategie
- Design-Details
- Interpreter-Initialisierungsphasen
- Aufruf der Phasen
- Uninitialisierter Zustand
- Laufzeit-Vorinitialisierungsphase
- Bestimmung der verbleibenden Konfigurationseinstellungen
- Unterstützte Konfigurationseinstellungen
- Abschluss der Hauptinterpreter-Initialisierung
- Vorbereitung des Hauptmoduls
- Ausführung des Hauptmoduls
- Interne Speicherung von Konfigurationsdaten
- Erstellung und Konfiguration von Subinterpretern
- Stabile ABI
- Build-Zeit-Konfiguration
- Abwärtskompatibilität
- Ein System-Python-Executable
- Offene Fragen
- Implementierung
- Der Status Quo (Stand Python 3.6)
- Referenzen
- Urheberrecht
Rücknahme eines PEP
Von Ende 2012 bis Mitte 2020 bot dieses PEP allgemeinen Hintergrund und spezifische konkrete Vorschläge, um die CPython-Startsequenz wartungsfreundlicher zu gestalten und die CPython-Laufzeit einfacher in größere Anwendungen einzubetten.
Die meiste Zeit wurden die Änderungen entweder in einem separaten Feature-Branch oder als private APIs mit Unterstrich-Präfix im Haupt-CPython-Repository gepflegt.
Im Jahr 2019 migrierte PEP 587 eine Teilmenge dieser API-Änderungen in die öffentliche CPython-API für Python 3.8+ (insbesondere aktualisierte das PEP die Interpreterlaufzeit, um eine explizit mehrstufige struktur-basierte Konfigurationsschnittstelle anzubieten).
Im Juni 2020 entschieden die Autoren des PEPs in Reaktion auf eine Anfrage des Steering Council, dass es sinnvoll sei, das ursprüngliche PEP zurückzuziehen, da sich seit der ersten Niederschrift von PEP 432 genug geändert habe, um anzunehmen, dass weitere Änderungen an der Startsequenz und der Embedding-API am besten als ein neues PEP (oder mehrere PEPs) formuliert werden sollten, die nicht nur die noch nicht implementierten Ideen aus PEP 432 berücksichtigen, die nicht als ausreichend validiert erachtet wurden, um ihren Weg in PEP 587 zu finden, sondern auch jegliches Feedback zur öffentlichen PEP 587 API und alle weiteren Erkenntnisse, die bei der Anpassung der CPython-Implementierung für eine verbesserte Einbettbarkeit und Subinterpreter-Freundlichkeit gewonnen wurden.
Insbesondere PEPs, die die folgenden Änderungen vorschlagen, und alle weiteren Infrastrukturänderungen, die zu deren Ermöglichung erforderlich sind, wären wahrscheinlich weiterhin von Wert zur Erkundung:
- Auslieferung eines alternativen Python-Executables, das alle Benutzereinstellungen ignoriert und standardmäßig im isolierten Modus läuft, und daher für die Ausführung von System-Python-Anwendungen besser geeignet wäre als der Standardinterpreter.
- Erweiterung des `zipapp`-Moduls zur Unterstützung der Erstellung von Single-File-Executables aus reinem Python-Code (und potenziell sogar aus Python-Erweiterungsmodulen angesichts der Einführung einer mehrstufigen Initialisierung von Erweiterungsmodulen).
- Migration der komplexen `sys.path`-Initialisierungslogik von C nach Python, um die Testabdeckung und die allgemeine Wartbarkeit dieses Codes zu verbessern.
Zusammenfassung
Dieses PEP schlägt einen Mechanismus zur Umstrukturierung der Startsequenz für CPython vor, der es einfacher macht, das Initialisierungsverhalten des Referenzinterpreter-Executables zu ändern, sowie die Steuerung des Startverhaltens von CPython beim Erstellen eines alternativen Executables oder beim Einbetten als Python-Ausführungs-Engine in eine größere Anwendung zu erleichtern.
Nach Abschluss der Implementierung dieses Vorschlags wird der Interpreterstart aus drei klar getrennten und unabhängig konfigurierbaren Phasen bestehen:
- Python Core Runtime Vorinitialisierung
- Einrichtung des Speichermanagements
- Bestimmung der für System schnittstellen verwendeten Codierungen (einschließlich Einstellungen, die für spätere Konfigurationsphasen übergeben werden)
- Python Core Runtime Initialisierung
- Sicherstellung, dass die C-API einsatzbereit ist
- Sicherstellung, dass Builtin- und gefrorene Module zugänglich sind
- Hauptinterpreter-Konfiguration
- Sicherstellung, dass externe Module zugänglich sind
- (Hinweis: Der Name dieser Phase wird sich sehr wahrscheinlich ändern)
Es werden auch Änderungen vorgeschlagen, die die Ausführung des Hauptmoduls und die Subinterpreter-Initialisierung betreffen.
Hinweis: TBC = To Be Confirmed (Zu bestätigen), TBD = To Be Determined (Zu bestimmen). Die geeignete Auflösung für die meisten dieser Punkte wird klarer werden, wenn die Referenzimplementierung entwickelt wird.
Vorschlag
Dieses PEP schlägt vor, die Initialisierung der CPython-Laufzeit in drei klar getrennte Phasen aufzuteilen:
- Core Runtime Vorinitialisierung
- Core Runtime Initialisierung
- Hauptinterpreter-Konfiguration
(Frühere Versionen schlugen nur zwei Phasen vor, aber die Erfahrung bei dem Versuch, das PEP als interne CPython-Refaktorierung zu implementieren, zeigte, dass mindestens 3 Phasen erforderlich sind, um eine klare Trennung der Zuständigkeiten zu erreichen.)
Das vorgeschlagene Design hat auch erhebliche Auswirkungen auf
- die Ausführung des Hauptmoduls
- die Subinterpreter-Initialisierung
Im neuen Design durchläuft der Interpreter während der Initialisierungssequenz die folgenden gut definierten Phasen:
- Nicht initialisiert – die Vorinitialisierungsphase wurde noch nicht einmal begonnen.
- Vorinitialisierung – kein Interpreter verfügbar.
- Laufzeit initialisiert – Hauptinterpreter teilweise verfügbar, Subinterpreter-Erstellung noch nicht verfügbar.
- Initialisiert – Hauptinterpreter vollständig verfügbar, Subinterpreter-Erstellung verfügbar.
PEP 587 ist ein detaillierterer Vorschlag, der die Trennung der Vorinitialisierungsphase von den letzten beiden Phasen behandelt, aber keine Einbettungsanwendungen zulässt, beliebigen Code im Zustand "Laufzeit initialisiert" auszuführen (stattdessen wird die Initialisierung der Core Runtime immer auch den Hauptinterpreter vollständig initialisieren, da dies die Funktionsweise der nativen CPython CLI in Python 3.8 ist).
Als konkreter Anwendungsfall zur Unterstützung von Designänderungen und zur Lösung eines bekannten Problems, bei dem die geeigneten Standardwerte für System-Utilities von denen für das Ausführen von Benutzer-Skripten abweichen, schlägt dieses PEP die Erstellung und Verteilung eines separaten System-Python-Executables (`system-python`), das standardmäßig im "isolierten Modus" (ausgewählt mit dem CPython `-I`-Switch) operiert, sowie die Erstellung eines Beispiel-Stub-Binärprogramms vor, das einfach ein angehängtes Zip-Archiv ausführt (was Single-File-Pure-Python-Executables ermöglicht), anstatt die normale CPython-Startsequenz zu durchlaufen.
Um die Komplexität der Implementierung unter Kontrolle zu halten, schlägt dieses PEP *keine* umfassenden Änderungen an der Art und Weise vor, wie auf den Interpreter-Zustand zur Laufzeit zugegriffen wird. Das Ändern der Reihenfolge der vorhandenen Initialisierungsschritte, um die Startsequenz wartungsfreundlicher zu gestalten, ist bereits eine erhebliche Änderung, und der Versuch, diese anderen Änderungen gleichzeitig vorzunehmen, würde die Änderung erheblich invasiver und viel schwieriger zu überprüfen machen. Solche Vorschläge können jedoch geeignete Themen für Folge-PEPs oder Patches sein – ein wichtiger Vorteil dieses PEP und seiner zugehörigen Teilvorschläge ist die Verringerung der Kopplung zwischen dem internen Speichermodell und der Konfigurationsschnittstelle, sodass solche Änderungen einfacher sein sollten, sobald dieses PEP implementiert wurde.
Hintergrund
Im Laufe der Zeit wurde die Initialisierungssequenz von CPython immer komplizierter und bot mehr Optionen sowie komplexere Aufgaben (wie die Konfiguration der Unicode-Einstellungen für OS-Schnittstellen in Python 3 [10], das Bootstrapping einer reinen Python-Implementierung des Import-Systems und die Implementierung eines isolierten Modus, der für Systemanwendungen mit erhöhten Rechten besser geeignet ist [6]).
Ein Großteil dieser Komplexität ist nur über die APIs `Py_Main` und `Py_Initialize` formal zugänglich, was Einbettungsanwendungen nur wenig Möglichkeiten zur Anpassung bietet. Diese schleichende Komplexität erschwert auch den Maintainern das Leben, da ein Großteil der Konfiguration vor dem Aufruf von `Py_Initialize` stattfinden muss, was bedeutet, dass ein Großteil der Python C-API nicht sicher verwendet werden kann.
Eine Reihe von Vorschlägen liegen auf dem Tisch für noch *anspruchsvolleres* Startverhalten, wie z. B. bessere Kontrolle über die `sys.path`-Initialisierung (z. B. einfaches Hinzufügen zusätzlicher Verzeichnisse auf der Kommandozeile plattformübergreifend [7], Steuerung der Konfiguration von `sys.path[0]` [8]), einfachere Konfiguration von Utilities wie Coverage-Tracing beim Starten von Python-Subprozessen [9]).
Anstatt solches Verhalten auf unbestimmte Zeit an ein bereits komplexes System anzuhängen, schlägt dieses PEP vor, den Status Quo zu vereinfachen, indem eine strukturiertere Startsequenz eingeführt wird, mit dem Ziel, diese weiteren Funktionsanfragen einfacher implementierbar zu machen.
Ursprünglich wurde der gesamte Vorschlag in diesem einen PEP beibehalten, aber das erwies sich als unpraktisch. Daher werden, sobald sich Teile des vorgeschlagenen Designs stabilisiert haben, diese nun in eigene PEPs aufgeteilt, was Fortschritte ermöglicht, auch wenn die Details des Gesamtdesigns noch in Entwicklung sind.
Wesentliche Anliegen
Es gibt einige wesentliche Anliegen, die jede Änderung der Startsequenz berücksichtigen muss.
Wartbarkeit
Die CPython-Startsequenz ab Python 3.6 war schwer zu verstehen und noch schwerer zu ändern. Es war unklar, in welchem Zustand sich der Interpreter befand, während ein Großteil des Initialisierungscodes ausgeführt wurde, was zu Verhaltensweisen wie der Erstellung von Listen, Dictionaries und Unicode-Werten vor dem Aufruf von `Py_Initialize` führte, wenn die Optionen `-X` oder `-W` verwendet wurden [1].
Durch die Umstellung auf eine explizit mehrstufige Startsequenz sollten Entwickler nur noch verstehen müssen:
- welche APIs und Features vor der Vor-Konfiguration verfügbar sind (im Wesentlichen keine, außer der Vor-Konfigurations-API selbst)
- welche APIs und Features vor der Core Runtime Konfiguration verfügbar sind und die Vor-Konfiguration implizit mit Standardeinstellungen ausführen, die dem Verhalten von Python 3.6 entsprechen, wenn die Vor-Konfiguration nicht explizit ausgeführt wurde.
- welche APIs und Features erst nach vollständiger Konfiguration des Hauptinterpreters verfügbar sind (was hoffentlich eine relativ kleine Teilmenge der vollständigen C-API sein wird).
Die ersten beiden Aspekte davon werden von PEP 587 abgedeckt, während die Details der letzteren Unterscheidung noch geprüft werden.
Durch die Basis des neuen Designs auf einer Kombination aus C-Strukturen und Python-Datentypen sollte es auch einfacher sein, das System in Zukunft zu ändern, um neue Konfigurationsoptionen hinzuzufügen.
Testbarkeit
Eines der Probleme mit der Komplexität der CPython-Startsequenz ist die kombinatorische Explosion möglicher Interaktionen zwischen verschiedenen Konfigurationseinstellungen.
Dieses Anliegen beeinflusst sowohl das Design des neuen Initialisierungssystems als auch den vorgeschlagenen Ansatz, dorthin zu gelangen.
Performance
CPython wird häufig zum Ausführen kurzer Skripte verwendet, bei denen die Laufzeit von der Interpreter-Initialisierungszeit dominiert wird. Alle Änderungen an der Startsequenz sollten ihre Auswirkungen auf den Start-Overhead minimieren.
Die Erfahrungen mit der Importlib-Migration legen nahe, dass die Startzeit von E/A-Operationen dominiert wird. Um die Auswirkungen von Änderungen zu überwachen, kann jedoch ein einfaches Benchmark verwendet werden, um zu prüfen, wie lange es dauert, den Interpreter zu starten und dann wieder abzubauen.
python3 -m timeit -s "from subprocess import call" "call(['./python', '-Sc', 'pass'])"
Aktuelle Zahlen auf meinem System für Python 3.7 (wie vom Fedora-Projekt gebaut)
$ python3 -m timeit -s "from subprocess import call" "call(['python3', '-Sc', 'pass'])"
50 loops, best of 5: 6.48 msec per loop
(TODO: Dieses Microbenchmark mit perf statt des stdlib timeit ausführen)
Dieses PEP wird voraussichtlich keine signifikanten Auswirkungen auf die Startzeit haben, da es sich primär auf die *Neuordnung* der bestehenden Initialisierungssequenz konzentriert, ohne wesentliche Änderungen an den einzelnen Schritten vorzunehmen.
Wenn dieser einfache Check jedoch darauf hindeutet, dass die vorgeschlagenen Änderungen an der Initialisierungssequenz ein Leistungsproblem darstellen könnten, wird ein anspruchsvolleres Microbenchmark entwickelt, um bei der Untersuchung zu helfen.
Erforderliche Konfigurationseinstellungen
Siehe PEP 587 für eine detaillierte Auflistung der CPython-Interpreter-Konfigurationseinstellungen und der verschiedenen verfügbaren Mittel zu deren Einstellung.
Implementierungsstrategie
Ein erster Versuch zur Implementierung einer früheren Version dieses PEPs für Python 3.4 wurde unternommen [2]. Eines der dabei aufgetretenen signifikanten Probleme waren Merge-Konflikte, nachdem die anfänglichen strukturellen Änderungen zur Einleitung des Refactoring-Prozesses vorgenommen wurden. Im Gegensatz zu einigen anderen früheren größeren Änderungen, wie dem Wechsel zu einem AST-basierten Compiler in Python 2.5 oder dem Wechsel zum Importlib-Implementierung des Import-Systems in Python 3.3, gibt es keine klare Möglichkeit, eine Entwurfsimplementierung zu strukturieren, die nicht anfällig für die Arten von Merge-Konflikten wäre, die den ursprünglichen Versuch beeinträchtigten.
Folglich wurde die Implementierungsstrategie überarbeitet, um dieses Refactoring stattdessen zuerst als private API für CPython 3.7 zu implementieren und dann die Machbarkeit der Bereitstellung der neuen Funktionen und Strukturen als öffentliche API-Elemente in CPython 3.8 zu prüfen.
Nach dem ersten Merge migrierte Victor Stinner dann tatsächlich Einstellungen in die neue Struktur, um die Änderungen für den UTF-8-Modus von PEP 540 erfolgreich zu implementieren (was die Fähigkeit erforderte, alle Einstellungen zu verfolgen, die zuvor mit der Locale-Codierung dekodiert wurden, und sie stattdessen mit UTF-8 zu dekodieren). Eric Snow migrierte auch eine Reihe interner Subsysteme, um die Subinterpreter-Funktion robuster zu gestalten.
Diese Arbeit zeigte, dass das ursprünglich in diesem PEP vorgeschlagene detaillierte Design eine Reihe praktischer Probleme aufwies, so dass Victor eine verbesserte private API (inspiriert von einer früheren Iteration dieses PEP) entwarf und implementierte, die PEP 587 vorschlägt, in Python 3.8 in eine öffentliche API zu fördern.
Design-Details
Hinweis
Die API-Details sind hier noch stark im Fluss. Die Header-Dateien, die den aktuellen Stand der privaten API zeigen, sind hauptsächlich:
- https://github.com/python/cpython/blob/master/Include/cpython/coreconfig.h
- https://github.com/python/cpython/blob/master/Include/cpython/pystate.h
- https://github.com/python/cpython/blob/master/Include/cpython/pylifecycle.h
PEP 587 deckt die Aspekte der API ab, die als potenziell stabil genug erachtet werden, um sie öffentlich zu machen. Wo eine vorgeschlagene API von diesem PEP abgedeckt wird, wird "(siehe PEP 587)" dem Text unten hinzugefügt.
Das Hauptthema dieses Vorschlags ist die Initialisierung der Core Language Runtime und die Erstellung eines teilweise initialisierten Interpreterzustands für den Hauptinterpreter *viel* früher im Startprozess. Dies ermöglicht die Nutzung der meisten CPython-APIs während des restlichen Initialisierungsprozesses, was potenziell eine Reihe von Operationen vereinfacht, die derzeit auf grundlegende C-Funktionalität angewiesen sind, anstatt die reichhaltigeren Datenstrukturen der CPython C-API nutzen zu können.
PEP 587 deckt eine Teilmenge dieser Aufgabe ab, nämlich die Auslagerung der Komponenten, die selbst die vorhandenen Schnittstellen "Kann vor `Py_Initialize` aufgerufen werden" benötigen (wie Speicherallokatoren und Details zur Codierung der Betriebssystem-Schnittstellen), in einen separaten Vor-Konfigurationsschritt.
Im Folgenden deckt der Begriff "Einbettungsanwendung" auch die Standard-CPython-Kommandozeilenanwendung ab.
Interpreter-Initialisierungsphasen
Die folgenden unterschiedlichen Interpreter-Initialisierungsphasen werden vorgeschlagen:
- Nicht initialisiert
- Keine wirkliche Phase, sondern die Abwesenheit einer Phase.
Py_IsInitializing()gibt0zurück.Py_IsRuntimeInitialized()gibt0zurück.Py_IsInitialized()gibt0zurück.- Die Einbettungsanwendung bestimmt, welcher Speicherallokator verwendet wird und welche Codierung für den Zugriff auf Betriebssystem-Schnittstellen verwendet wird (oder delegiert diese Entscheidungen an die Python-Laufzeit).
- Die Anwendung startet den Initialisierungsprozess durch Aufruf einer der `
Py_PreInitialize`-APIs (siehe PEP 587).
- Laufzeit-Vorinitialisierung
- Kein Interpreter ist verfügbar.
Py_IsInitializing()gibt1zurück.Py_IsRuntimeInitialized()gibt0zurück.Py_IsInitialized()gibt0zurück.- Die Einbettungsanwendung bestimmt die Einstellungen, die zur Initialisierung der CPython-Kernlaufzeit und zur Erstellung des Hauptinterpreters erforderlich sind, und wechselt zur nächsten Phase durch Aufruf von `
Py_InitializeRuntime`. - Hinweis: Ab PEP 587 ruft die Einbettungsanwendung stattdessen `
Py_Main()`, `Py_UnixMain` oder eine der `Py_Initialize`-APIs auf und springt somit direkt zum initialisierten Zustand.
- Hauptinterpreter-Initialisierung
- Die Builtin-Datentypen und andere Kernlaufzeitdienste sind verfügbar.
- Der Hauptinterpreter ist verfügbar, aber nur teilweise konfiguriert.
Py_IsInitializing()gibt1zurück.Py_IsRuntimeInitialized()gibt1zurück.Py_IsInitialized()gibt0zurück.- Die Einbettungsanwendung bestimmt und wendet die Einstellungen an, die zur Vervollständigung des Initialisierungsprozesses erforderlich sind, indem sie `
Py_InitializeMainInterpreter` aufruft. - Hinweis: Ab PEP 587 ist dieser Zustand über keine öffentliche API erreichbar; er existiert nur als impliziter interner Zustand, während eine der `
Py_Initialize`-Funktionen ausgeführt wird.
- Initialisiert
- Der Hauptinterpreter ist verfügbar und voll funktionsfähig, aber Metadaten im Zusammenhang mit `__main__` sind unvollständig.
Py_IsInitializing()gibt0zurück.Py_IsRuntimeInitialized()gibt1zurück.Py_IsInitialized()gibt1zurück.
Aufruf der Phasen
Alle aufgeführten Phasen werden vom Standard-CPython-Interpreter und dem vorgeschlagenen System-Python-Interpreter verwendet.
Eine Einbettungsanwendung kann die Initialisierung weiterhin fast vollständig unter der Kontrolle von CPython belassen, indem sie die vorhandenen APIs `Py_Initialize` oder `Py_Main()` verwendet – die Abwärtskompatibilität wird erhalten bleiben.
Alternativ kann eine Einbettungsanwendung, wenn sie mehr Kontrolle über den initialen Zustand von CPython wünscht, die neue, feingranularere API verwenden, die der Einbettungsanwendung mehr Kontrolle über den Initialisierungsprozess ermöglicht.
PEP 587 deckt eine anfängliche Iteration dieser API ab, die die Vorinitialisierungsphase trennt, ohne zu versuchen, die Kernlaufzeitinitialisierung von der Hauptinterpreter-Initialisierung zu trennen.
Uninitialisierter Zustand
Der nicht initialisierte Zustand ist derjenige, in dem eine Einbettungsanwendung die Einstellungen ermittelt, die erforderlich sind, um dem eingebetteten Python-Runtime korrekte Konfigurationseinstellungen zu übergeben.
Dies beinhaltet die Angabe, welcher Speicherallokator verwendet werden soll, sowie welche Textcodierung bei der Verarbeitung von bereitgestellten Einstellungen verwendet werden soll.
PEP 587 definiert die für diesen Zustand erforderlichen Einstellungen in seiner `PyPreConfig`-Struktur.
Eine neue Abfrage-API ermöglicht es dem Code zu ermitteln, ob der Interpreter den Initialisierungsprozess noch nicht einmal begonnen hat.
int Py_IsInitializing();
Die Abfrage für eine vollständig uninitialisierte Umgebung wäre dann `!(Py_Initialized() || Py_Initializing())`.
Laufzeit-Vorinitialisierungsphase
Hinweis
In PEP 587 sind die Einstellungen für diese Phase noch nicht getrennt, sondern nur über die kombinierte `PyConfig`-Struktur verfügbar.
Die Vorinitialisierungsphase ist die Phase, in der eine Einbettungsanwendung die Einstellungen ermittelt, die absolut erforderlich sind, bevor die CPython-Laufzeit überhaupt initialisiert werden kann. Derzeit sind die primären Konfigurationseinstellungen in dieser Kategorie diejenigen, die sich auf den randomisierten Hash-Algorithmus beziehen – die Hash-Algorithmen müssen für die Lebensdauer des Prozesses konsistent sein und daher vor der Erstellung des Kerninterpreters vorhanden sein.
Die wesentlichen Einstellungen sind ein Flag, das angibt, ob ein bestimmter Seed-Wert für die randomisierten Hashes verwendet werden soll, und falls ja, der spezifische Wert für den Seed (ein Seed-Wert von Null deaktiviert die randomisierte Hashing). Darüber hinaus muss aufgrund der möglichen Verwendung von `PYTHONHASHSEED` bei der Konfiguration der Hash-Randomisierung auch die Frage, ob Umgebungsvariablen berücksichtigt werden sollen, frühzeitig behandelt werden. Schließlich wird zur Unterstützung des CPython-Build-Prozesses eine Option angeboten, das Importsystem vollständig zu deaktivieren.
Die vorgeschlagenen APIs für diesen Schritt in der Startsequenz sind:
PyInitError Py_InitializeRuntime(
const PyRuntimeConfig *config
);
PyInitError Py_InitializeRuntimeFromArgs(
const PyRuntimeConfig *config, int argc, char **argv
);
PyInitError Py_InitializeRuntimeFromWideArgs(
const PyRuntimeConfig *config, int argc, wchar_t **argv
);
Wenn `Py_IsInitializing()` falsch ist, rufen die `Py_InitializeRuntime`-Funktionen implizit die entsprechende `Py_PreInitialize`-Funktion auf. Die Einstellung `use_environment` wird weitergegeben, während andere Einstellungen gemäß ihren Standardwerten verarbeitet werden, wie in PEP 587 beschrieben.
Der Rückgabetyp `PyInitError` ist in PEP 587 definiert und ermöglicht es einer Einbettungsanwendung, Fehler bei der Initialisierung der Python-Laufzeit ordnungsgemäß zu behandeln, anstatt den gesamten Prozess abrupt durch `Py_FatalError` beenden zu lassen.
Die neue Struktur `PyRuntimeConfig` enthält die Einstellungen, die für die vorläufige Konfiguration der Kernlaufzeit und die Erstellung des Hauptinterpreters erforderlich sind.
/* Note: if changing anything in PyRuntimeConfig, also update
* PyRuntimeConfig_INIT */
typedef struct {
bool use_environment; /* as in PyPreConfig, PyConfig from PEP 587 */
int use_hash_seed; /* PYTHONHASHSEED, as in PyConfig from PEP 587 */
unsigned long hash_seed; /* PYTHONHASHSEED, as in PyConfig from PEP 587 */
bool _install_importlib; /* Needed by freeze_importlib */
} PyRuntimeConfig;
/* Rely on the "designated initializer" feature of C99 */
#define PyRuntimeConfig_INIT {.use_hash_seed=-1}
Der Zeiger auf die Kernkonfigurationseinstellungen kann `NULL` sein, in diesem Fall sind die Standardwerte wie in `PyRuntimeConfig_INIT` angegeben.
Das Makro `PyRuntimeConfig_INIT` ist so konzipiert, dass es eine einfache Initialisierung einer Strukturinstanz mit sinnvollen Standardwerten ermöglicht.
PyRuntimeConfig runtime_config = PyRuntimeConfig_INIT;
`use_environment` steuert die Verarbeitung aller Python-bezogenen Umgebungsvariablen. Wenn das Flag gesetzt ist, wird `PYTHONHASHSEED` normal verarbeitet. Andernfalls werden alle Python-spezifischen Umgebungsvariablen als undefiniert betrachtet (Ausnahmen können für einige OS-spezifische Umgebungsvariablen gemacht werden, wie z. B. die, die unter Mac OS X zur Kommunikation zwischen dem App-Bundle und dem Haupt-Python-Binärprogramm verwendet werden).
`use_hash_seed` steuert die Konfiguration des randomisierten Hash-Algorithmus. Wenn er null ist, werden randomisierte Hashes mit einem zufälligen Seed verwendet. Wenn er positiv ist, wird der Wert in `hash_seed` zur Initialisierung des Zufallszahlengenerators verwendet. Wenn der `hash_seed` in diesem Fall null ist, wird das randomisierte Hashing vollständig deaktiviert.
Wenn `use_hash_seed` negativ ist (und `use_environment` wahr ist), prüft CPython die Umgebungsvariable `PYTHONHASHSEED`. Wenn die Umgebungsvariable nicht gesetzt ist, auf einen leeren String gesetzt ist oder auf den Wert `"random"` gesetzt ist, werden randomisierte Hashes mit einem zufälligen Seed verwendet. Wenn die Umgebungsvariable auf den String `"0"` gesetzt ist, wird das randomisierte Hashing deaktiviert. Andernfalls wird erwartet, dass der Hash-Seed eine String-Repräsentation einer Ganzzahl im Bereich `[0; 4294967295]` ist.
Um es Einbettungsanwendungen zu erleichtern, die Verarbeitung von `PYTHONHASHSEED` mit einer anderen Datenquelle zu verwenden, wird die folgende Hilfsfunktion zur C-API hinzugefügt:
int Py_ReadHashSeed(char *seed_text,
int *use_hash_seed,
unsigned long *hash_seed);
Diese Funktion akzeptiert einen Seed-Text in `seed_text` und wandelt ihn in die entsprechenden Flag- und Seed-Werte um. Wenn `seed_text` `NULL` ist, ein leerer String oder der Wert `"random"`, werden sowohl `use_hash_seed` als auch `hash_seed` auf Null gesetzt. Andernfalls wird `use_hash_seed` auf `1` gesetzt und der Seed-Text wird als Ganzzahl interpretiert und als `hash_seed` gemeldet. Bei Erfolg gibt die Funktion Null zurück. Ein von Null verschiedener Rückgabewert zeigt einen Fehler an (höchstwahrscheinlich bei der Umwandlung in eine Ganzzahl).
Die Einstellung `_install_importlib` wird als Teil des CPython-Build-Prozesses verwendet, um einen Interpreter ohne Importfunktion zu erstellen. Sie gilt als privat für das CPython-Entwicklungsteam (daher der führende Unterstrich), da der einzige derzeit unterstützte Anwendungsfall die Ermöglichung von Compiler-Änderungen ist, die die zuvor eingefrorenen Bytecodes für `importlib._bootstrap` ungültig machen, ohne den Build-Prozess zu beeinträchtigen.
Das Ziel ist es, dieses anfängliche Konfigurationsniveau so klein wie möglich zu halten, um die Bootstrap-Umgebung über verschiedene Einbettungsanwendungen hinweg konsistent zu halten. Wenn wir einen gültigen Interpreter-Zustand ohne die Einstellung erstellen können, dann sollte die Einstellung nur in der umfassenden `PyConfig`-Struktur und nicht in der Kernlaufzeitkonfiguration erscheinen.
Eine neue Abfrage-API ermöglicht es dem Code zu ermitteln, ob sich der Interpreter im Bootstrap-Zustand zwischen der Kernlaufzeitinitialisierung und der Erstellung des Hauptinterpreterzustands sowie dem Abschluss des Großteils des Hauptinterpreterinitialisierungsprozesses befindet.
int Py_IsRuntimeInitialized();
Der Versuch, `Py_InitializeRuntime()` erneut aufzurufen, wenn `Py_IsRuntimeInitialized()` bereits wahr ist, wird als Benutzerkonfigurationsfehler gemeldet. (TBC, da bestehende öffentliche Initialisierungs-APIs mehrmals ohne Fehler aufgerufen werden können und Änderungen an schreibgeschützten Einstellungen einfach ignorieren. Es könnte sinnvoll sein, dieses Verhalten beizubehalten, anstatt zu versuchen, die neue API strenger zu gestalten als die alte.)
Da gefrorene Bytecodes nun legitim in einem Interpreter ausgeführt werden können, der noch nicht vollständig initialisiert ist, erhält `sys.flags` ein neues Flag `initialized`.
Mit der initialisierten Kernlaufzeit sollte der Hauptinterpreter und der Großteil der CPython C-API voll funktionsfähig sein, außer dass
- Kompilierung nicht erlaubt ist (da Parser und Compiler noch nicht richtig konfiguriert sind).
- Erstellung von Subinterpretern nicht erlaubt ist.
- Erstellung zusätzlicher Thread-Zustände nicht erlaubt ist.
- Die folgenden Attribute im Modul `sys` fehlen entweder oder sind `None`: * `sys.path` * `sys.argv` * `sys.executable` * `sys.base_exec_prefix` * `sys.base_prefix` * `sys.exec_prefix` * `sys.prefix` * `sys.warnoptions` * `sys.dont_write_bytecode` * `sys.stdin` * `sys.stdout`
- Die Dateisystem-Codierung ist noch nicht definiert.
- Die E/A-Codierung ist noch nicht definiert.
- CPython-Signalhandler sind noch nicht installiert.
- Nur Builtin- und gefrorene Module dürfen importiert werden (aufgrund der oben genannten Einschränkungen).
- `sys.stderr` ist auf ein temporäres E/A-Objekt im unbuffered Binary-Modus gesetzt.
- Das Attribut `sys.flags` existiert, aber die einzelnen Flags haben möglicherweise noch nicht ihre endgültigen Werte.
- Das Attribut `sys.flags.initialized` ist auf `0` gesetzt.
- Das Modul `warnings` ist noch nicht initialisiert.
- Das Modul
__main__existiert noch nicht
<TBD: Identifizieren Sie weitere bemerkenswerte fehlende Funktionalitäten>
Die Hauptbestandteile, die durch diesen Schritt verfügbar gemacht werden, sind die Kern-Python-Datentypen, insbesondere Dictionaries, Listen und Strings. Dies ermöglicht deren sichere Verwendung für alle verbleibenden Konfigurationsschritte (im Gegensatz zum Status quo).
Darüber hinaus verfügt der aktuelle Thread über einen gültigen Python-Thread-Zustand, sodass weitere Konfigurationsdaten im Hauptinterpreterobjekt und nicht in globalen C-Prozessvariablen gespeichert werden können.
Jeder Aufruf von Py_InitializeRuntime() muss einen entsprechenden Aufruf von Py_Finalize() haben. Es ist akzeptabel, den Aufruf von Py_InitializeMainInterpreter() dazwischen zu überspringen (z. B. wenn der Versuch, die Haupteinstellungen für die Interpreterkonfiguration zu erstellen, fehlschlägt).
Bestimmung der verbleibenden Konfigurationseinstellungen
Der nächste Schritt in der Initialisierungssequenz besteht darin, die verbleibenden Einstellungen zu ermitteln, die zur Fertigstellung des Prozesses erforderlich sind. Zu diesem Zeitpunkt werden keine Änderungen am Interpreterzustand vorgenommen. Die Kern-APIs für diesen Schritt sind
int Py_BuildPythonConfig(
PyConfigAsObjects *py_config, const PyConfig *c_config
);
int Py_BuildPythonConfigFromArgs(
PyConfigAsObjects *py_config, const PyConfig *c_config, int argc, char **argv
);
int Py_BuildPythonConfigFromWideArgs(
PyConfigAsObjects *py_config, const PyConfig *c_config, int argc, wchar_t **argv
);
Das Argument py_config sollte ein Zeiger auf eine PyConfigAsObjects-Struktur sein (die eine temporäre, auf dem C-Stack gespeicherte Struktur sein kann). Für jeden bereits konfigurierten Wert (d. h. jeden Nicht-NULL-Zeiger) wird CPython den bereitgestellten Wert auf Plausibilität prüfen, ihn aber ansonsten als korrekt akzeptieren.
Eine Struktur wird anstelle eines Python-Dictionaries verwendet, da die Struktur einfacher aus C zu handhaben ist, die Liste der unterstützten Felder für eine gegebene CPython-Version fest ist und nur eine schreibgeschützte Ansicht für Python-Code bereitgestellt werden muss (was dank der bereits vorhandenen Infrastruktur zur Bereitstellung von sys.implementation relativ einfach ist).
Im Gegensatz zu Py_InitializeRuntime löst dieser Aufruf bei einem Problem mit den Konfigurationsdaten eine Python-Ausnahme aus und meldet eine Fehler-Rückgabe, anstatt eine Python-initialisierungsspezifische C-Struktur zurückzugeben.
Jede unterstützte Konfigurationseinstellung, die noch nicht gesetzt ist, wird in der bereitgestellten Konfigurationsstruktur entsprechend befüllt. Die Standardkonfiguration kann vollständig überschrieben werden, indem der Wert *vor* dem Aufruf von Py_BuildPythonConfig gesetzt wird. Der bereitgestellte Wert wird dann auch zur Berechnung aller anderen von diesem Wert abgeleiteten Einstellungen verwendet.
Alternativ können Einstellungen auch *nach* dem Aufruf von Py_BuildPythonConfig überschrieben werden (dies kann nützlich sein, wenn eine einbettende Anwendung eine Einstellung anpassen möchte, anstatt sie vollständig zu ersetzen, wie z. B. das Entfernen von sys.path[0]).
Das Argument c_config ist ein optionaler Zeiger auf eine PyConfig-Struktur, wie in PEP 587 definiert. Wenn sie bereitgestellt wird, wird sie bevorzugt verwendet, anstatt Einstellungen direkt aus der Umgebung oder dem globalen Prozesszustand zu lesen.
Das bloße Lesen der Konfiguration hat keine Auswirkung auf den Interpreterzustand: Es modifiziert nur die übergebene Konfigurationsstruktur. Die Einstellungen werden erst beim Aufruf von Py_InitializeMainInterpreter (siehe unten) auf den laufenden Interpreter angewendet.
Unterstützte Konfigurationseinstellungen
Die Interpreterkonfiguration ist in zwei Teile unterteilt: Einstellungen, die entweder nur für den Hauptinterpreter relevant sind oder zwischen dem Hauptinterpreter und allen Unterinterpretern identisch sein müssen, und Einstellungen, die zwischen Unterinterpretern variieren können.
HINWEIS: Für die ersten Implementierungszwecke wird nur das Flag, das angibt, ob der Interpreter der Hauptinterpreter ist oder nicht, pro Interpreter konfiguriert. Andere Felder werden im Laufe der Implementierung daraufhin überprüft, ob sie machbar als interpreterspezifisch gemacht werden können.
Hinweis
Die Liste der nachfolgenden Konfigurationsfelder ist derzeit nicht mit PEP 587 synchronisiert. Wo sie abweichen, hat PEP 587 Vorrang.
Die Struktur PyConfigAsObjects spiegelt die Struktur PyConfig aus PEP 587 wider, verwendet jedoch vollständige Python-Objekte anstelle von C-Datentypen zur Speicherung von Werten. Sie fügt die Listenfelder raw_argv und argv hinzu, sodass spätere Initialisierungsschritte diese nicht separat annehmen müssen.
Felder sind immer Zeiger auf Python-Datentypen, wobei nicht gesetzte Werte durch NULL angezeigt werden
typedef struct {
/* Argument processing */
PyListObject *raw_argv;
PyListObject *argv;
PyListObject *warnoptions; /* -W switch, PYTHONWARNINGS */
PyDictObject *xoptions; /* -X switch */
/* Filesystem locations */
PyUnicodeObject *program_name;
PyUnicodeObject *executable;
PyUnicodeObject *prefix; /* PYTHONHOME */
PyUnicodeObject *exec_prefix; /* PYTHONHOME */
PyUnicodeObject *base_prefix; /* pyvenv.cfg */
PyUnicodeObject *base_exec_prefix; /* pyvenv.cfg */
/* Site module */
PyBoolObject *enable_site_config; /* -S switch (inverted) */
PyBoolObject *no_user_site; /* -s switch, PYTHONNOUSERSITE */
/* Import configuration */
PyBoolObject *dont_write_bytecode; /* -B switch, PYTHONDONTWRITEBYTECODE */
PyBoolObject *ignore_module_case; /* PYTHONCASEOK */
PyListObject *import_path; /* PYTHONPATH (etc) */
/* Standard streams */
PyBoolObject *use_unbuffered_io; /* -u switch, PYTHONUNBUFFEREDIO */
PyUnicodeObject *stdin_encoding; /* PYTHONIOENCODING */
PyUnicodeObject *stdin_errors; /* PYTHONIOENCODING */
PyUnicodeObject *stdout_encoding; /* PYTHONIOENCODING */
PyUnicodeObject *stdout_errors; /* PYTHONIOENCODING */
PyUnicodeObject *stderr_encoding; /* PYTHONIOENCODING */
PyUnicodeObject *stderr_errors; /* PYTHONIOENCODING */
/* Filesystem access */
PyUnicodeObject *fs_encoding;
/* Debugging output */
PyBoolObject *debug_parser; /* -d switch, PYTHONDEBUG */
PyLongObject *verbosity; /* -v switch */
/* Code generation */
PyLongObject *bytes_warnings; /* -b switch */
PyLongObject *optimize; /* -O switch */
/* Signal handling */
PyBoolObject *install_signal_handlers;
/* Implicit execution */
PyUnicodeObject *startup_file; /* PYTHONSTARTUP */
/* Main module
*
* If prepare_main is set, at most one of the main_* settings should
* be set before calling PyRun_PrepareMain (Py_ReadMainInterpreterConfig
* will set one of them based on the command line arguments if
* prepare_main is non-zero when that API is called).
PyBoolObject *prepare_main;
PyUnicodeObject *main_source; /* -c switch */
PyUnicodeObject *main_path; /* filesystem path */
PyUnicodeObject *main_module; /* -m switch */
PyCodeObject *main_code; /* Run directly from a code object */
PyObject *main_stream; /* Run from stream */
PyBoolObject *run_implicit_code; /* Run implicit code during prep */
/* Interactive main
*
* Note: Settings related to interactive mode are very much in flux.
*/
PyObject *prompt_stream; /* Output interactive prompt */
PyBoolObject *show_banner; /* -q switch (inverted) */
PyBoolObject *inspect_main; /* -i switch, PYTHONINSPECT */
} PyConfigAsObjects;
Die Struktur PyInterpreterConfig speichert die Einstellungen, die zwischen dem Hauptinterpreter und den Unterinterpretern variieren können. Für den Hauptinterpreter werden diese Einstellungen automatisch von Py_InitializeMainInterpreter() befüllt.
typedef struct {
PyBoolObject *is_main_interpreter; /* Easily check for subinterpreters */
} PyInterpreterConfig;
Da diese Strukturen ausschließlich aus Objektzeigern bestehen, sind keine expliziten Initialisierer-Definitionen erforderlich – die Standardinitialisierung des Struktur-Speichers auf Null nach C99 ist ausreichend.
Abschluss der Hauptinterpreter-Initialisierung
Der letzte Schritt im Initialisierungsprozess besteht darin, die Konfigurationseinstellungen tatsächlich wirksam zu machen und den Hauptinterpreter vollständig hochzufahren.
int Py_InitializeMainInterpreter(const PyConfigAsObjects *config);
Wie Py_BuildPythonConfig löst dieser Aufruf eine Ausnahme aus und meldet eine Fehler-Rückgabe, anstatt fatale Fehler anzuzeigen, wenn ein Problem mit den Konfigurationsdaten gefunden wird. (TBC, da bestehende öffentliche Initialisierungs-APIs mehrmals ohne Fehler aufgerufen werden können und Änderungen an schreibgeschützten Einstellungen einfach ignorieren. Es könnte sinnvoll sein, dieses Verhalten beizubehalten, anstatt die neue API strenger zu gestalten als die alte.)
Alle Konfigurationseinstellungen sind erforderlich – die Konfigurationsstruktur sollte immer durch Py_BuildPythonConfig geleitet werden, um sicherzustellen, dass sie vollständig befüllt ist.
Nach einem erfolgreichen Aufruf ist Py_IsInitialized() wahr und Py_IsInitializing() ist falsch. Die oben beschriebenen Vorbehalte für den Interpreter während der Phase, in der nur die Kernlaufzeit initialisiert wird, gelten dann nicht mehr.
Der Versuch, Py_InitializeMainInterpreter() erneut aufzurufen, wenn Py_IsInitialized() wahr ist, ist ein Fehler.
Einige Metadaten im Zusammenhang mit dem Modul __main__ können jedoch noch unvollständig sein
sys.argv[0]hat möglicherweise noch nicht seinen endgültigen Wert- es wird
-msein, wenn ein Modul oder Paket mit CPython ausgeführt wird - es wird dasselbe wie
sys.path[0]sein und nicht der Speicherort des Moduls__main__, wenn ein gültigersys.path-Eintrag (typischerweise eine Zip-Datei oder ein Verzeichnis) ausgeführt wird - andernfalls ist er korrekt
- der Skriptname, wenn ein normales Skript ausgeführt wird
-c, wenn ein bereitgestellter String ausgeführt wird-oder die leere Zeichenkette, wenn von stdin gelesen wird
- es wird
- die Metadaten im Modul
__main__geben immer noch an, dass es sich um ein Builtin-Modul handelt
Diese Funktion importiert standardmäßig das `site`-Modul als letzte Aktion (nachdem Py_IsInitialized() bereits gesetzt ist). Das Setzen des Flags „enable_site_config“ auf Py_False in den Konfigurationseinstellungen deaktiviert dieses Verhalten und eliminiert auch jegliche Nebeneffekte auf den globalen Zustand, wenn import site später explizit im Prozess ausgeführt wird.
Vorbereitung des Hauptmoduls
Hinweis
In PEP 587 werden PyRun_PrepareMain und PyRun_ExecMain nicht separat exponiert, sondern stattdessen über eine Py_RunMain API zugänglich gemacht, die sowohl den Hauptteil vorbereitet und ausführt als auch anschließend den Python-Interpreter finalisiert.
Diese Teilphase schließt die Befüllung der Metadaten im Zusammenhang mit dem Modul __main__ ab, ohne tatsächlich mit der Ausführung des Codes des Moduls __main__ zu beginnen.
Sie wird durch den Aufruf der folgenden API behandelt
int PyRun_PrepareMain();
Diese Operation ist nur für den Hauptinterpreter gestattet und löst bei Aufruf von einem Thread aus, dessen aktueller Thread-Status zu einem Unterinterpreter gehört, eine RuntimeError aus.
Die eigentliche Verarbeitung wird durch die Haupteinstellungen gesteuert, die als Teil der Konfigurationsstruktur im Interpreterzustand gespeichert sind.
Wenn prepare_main null ist, tut dieser Aufruf nichts.
Wenn main_source, main_path, main_module, main_stream und main_code alle NULL sind, tut dieser Aufruf nichts.
Wenn mehr als einer der Parameter main_source, main_path, main_module, main_stream oder main_code gesetzt ist, wird eine RuntimeError gemeldet.
Wenn main_code bereits gesetzt ist, tut dieser Aufruf nichts.
Wenn main_stream gesetzt ist und run_implicit_code ebenfalls gesetzt ist, wird die in startup_file identifizierte Datei kompiliert und im __main__-Namensraum ausgeführt.
Wenn main_source, main_path oder main_module gesetzt sind, ergreift dieser Aufruf alle notwendigen Schritte, um main_code zu befüllen
- Für
main_sourcewird der bereitgestellte String kompiliert und inmain_codegespeichert. - Für
main_path- wenn der bereitgestellte Pfad als gültiger
sys.path-Eintrag erkannt wird, wird er alssys.path[0]eingefügt,main_modulewird auf__main__gesetzt und die Verarbeitung wird fortgesetzt wie fürmain_moduleunten. - andernfalls wird der Pfad als CPython-Bytecode-Datei gelesen
- wenn dies fehlschlägt, wird er als Python-Quelldatei gelesen und kompiliert
- in den beiden letzteren Fällen wird das Code-Objekt in
main_codegespeichert und__main__.__file__entsprechend gesetzt
- wenn der bereitgestellte Pfad als gültiger
- Für
main_module- wird das übergeordnete Paket importiert
- wird der Loader für das Modul ermittelt
- wenn der Loader angibt, dass das Modul ein Paket ist, wird
.__main__am Ende vonmain_moduleangehängt und erneut versucht (wenn das letzte Namenssegment bereits.__main__ist, schlägt dies sofort fehl) - Sobald der Quellcode des Moduls gefunden wurde, wird der kompilierte Modulcode als
main_codegespeichert und die folgenden Attribute in__main__entsprechend befüllt:__name__,__loader__,__file__,__cached__,__package__.
(Hinweis: Das in diesem Abschnitt beschriebene Verhalten ist nicht neu, sondern eine Beschreibung des aktuellen Verhaltens des CPython-Interpreters, angepasst an das neue Konfigurationssystem.)
Ausführung des Hauptmoduls
Hinweis
In PEP 587 werden PyRun_PrepareMain und PyRun_ExecMain nicht separat exponiert, sondern stattdessen über eine Py_RunMain API zugänglich gemacht, die sowohl den Hauptteil vorbereitet und ausführt als auch anschließend den Python-Interpreter finalisiert.
Diese Teilphase umfasst die Ausführung des eigentlichen Codes des Moduls __main__.
Sie wird durch den Aufruf der folgenden API behandelt
int PyRun_ExecMain();
Diese Operation ist nur für den Hauptinterpreter gestattet und löst bei Aufruf von einem Thread aus, dessen aktueller Thread-Status zu einem Unterinterpreter gehört, eine RuntimeError aus.
Die eigentliche Verarbeitung wird durch die Haupteinstellungen gesteuert, die als Teil der Konfigurationsstruktur im Interpreterzustand gespeichert sind.
Wenn sowohl main_stream als auch main_code NULL sind, tut dieser Aufruf nichts.
Wenn sowohl main_stream als auch main_code gesetzt sind, wird eine RuntimeError gemeldet.
Wenn main_stream und prompt_stream beide gesetzt sind, wird die Hauptausführung an eine neue interne API delegiert
int _PyRun_InteractiveMain(PyObject *input, PyObject* output);
Wenn main_stream gesetzt ist und prompt_stream NULL ist, wird die Hauptausführung an eine neue interne API delegiert
int _PyRun_StreamInMain(PyObject *input);
Wenn main_code gesetzt ist, wird die Hauptausführung an eine neue interne API delegiert
int _PyRun_CodeInMain(PyCodeObject *code);
Nachdem die Ausführung von `main` abgeschlossen ist, ruft PyRun_ExecMain _PyRun_InteractiveMain(sys.__stdin__, sys.__stdout__) auf, wenn inspect_main gesetzt ist oder die Umgebungsvariable PYTHONINSPECT gesetzt wurde.
Interne Speicherung von Konfigurationsdaten
Der Interpreterzustand wird aktualisiert, um Details der während der Initialisierung bereitgestellten Konfigurationseinstellungen aufzunehmen, indem das Interpreterzustandsobjekt um mindestens eine eingebettete Kopie der Strukturen PyConfigAsObjects und PyInterpreterConfig erweitert wird.
Zu Debugging-Zwecken werden die Konfigurationseinstellungen als einfacher Namensraum sys._configuration (ähnlich wie sys.flags und sys.implementation) bereitgestellt. Die Attribute sind selbst einfache Namensräume, die den beiden Ebenen der Konfigurationseinstellung entsprechen
alle_Interpreteraktiver_Interpreter
Feldnamen entsprechen denen in den Konfigurationsstrukturen, mit Ausnahme von hash_seed, das bewusst ausgeschlossen wird.
Ein unterstrichenes Attribut wird bewusst gewählt, da diese Konfigurationseinstellungen Teil der CPython-Implementierung und nicht Teil der Python-Sprachdefinition sind. Wenn neue Einstellungen zur Unterstützung der plattformübergreifenden Kompatibilität in der Standardbibliothek benötigt werden, sollten diese mit den anderen Implementierungen abgestimmt und als neue erforderliche Attribute für sys.implementation bereitgestellt werden, wie in PEP 421 beschrieben.
Dies sind *Momentaufnahmen* der anfänglichen Konfigurationseinstellungen. Sie werden vom Interpreter während der Laufzeit nicht geändert (außer wie oben erwähnt).
Erstellung und Konfiguration von Subinterpretern
Da die neuen Konfigurationseinstellungen im Interpreterzustand gespeichert werden, müssen sie bei der Erstellung eines neuen Unterinterpreters initialisiert werden. Dies erweist sich als schwieriger als erwartet aufgrund von PyThreadState_Swap(NULL); (was glücklicherweise durch CPythons eigene Einbettungstests abgedeckt wird und dieses Problem während der Entwicklung erkannt werden kann).
Um eine einfache Lösung für diesen Fall zu bieten, schlägt die PEP die Hinzufügung einer neuen API vor
Py_InterpreterState *Py_InterpreterState_Main();
Dies wird ein Gegenstück zu Py_InterpreterState_Head() sein und meldet den ältesten aktuell vorhandenen Interpreter anstelle des neuesten. Wenn Py_NewInterpreter() von einem Thread mit einem vorhandenen Thread-Status aufgerufen wird, wird die Interpreterkonfiguration für diesen Thread bei der Initialisierung des neuen Unterinterpreters verwendet. Wenn kein aktueller Thread-Status vorhanden ist, wird die Konfiguration von Py_InterpreterState_Main() verwendet.
Während die bestehende API Py_InterpreterState_Head() stattdessen verwendet werden könnte, ändert sich diese Referenz, während Unterinterpreter erstellt und zerstört werden, während PyInterpreterState_Main() immer auf den anfänglichen Interpreterzustand verweisen wird, der in Py_InitializeRuntime() erstellt wurde.
Eine neue Einschränkung wird auch zur Embedding-API hinzugefügt: Der Versuch, den Hauptinterpreter zu löschen, während noch Unterinterpreter existieren, ist nun ein fataler Fehler.
Stabile ABI
Die meisten der in dieser PEP vorgeschlagenen APIs sind von der stabilen ABI ausgeschlossen, da das Einbetten eines Python-Interpreters einen weitaus höheren Grad an Kopplung erfordert als das reine Schreiben eines Erweiterungsmoduls.
Die einzigen neu exponierten APIs, die Teil der stabilen ABI sein werden, sind die Abfragen Py_IsInitializing() und Py_IsRuntimeInitialized().
Build-Zeit-Konfiguration
Diese PEP ändert nichts an der Behandlung von Konfigurationseinstellungen zur Build-Zeit und hat somit keine Auswirkung auf den Inhalt von sys.implementation oder das Ergebnis von sysconfig.get_config_vars().
Abwärtskompatibilität
Die Abwärtskompatibilität wird hauptsächlich dadurch gewährleistet, dass Py_BuildPythonConfig() alle zuvor definierten Konfigurationseinstellungen abfragt, die in globalen und Umgebungsvariablen gespeichert sind, und dass Py_InitializeMainInterpreter() betroffene Einstellungen in die relevanten Orte zurückschreibt.
Eine anerkannte Inkompatibilität besteht darin, dass einige Umgebungsvariablen, die derzeit verzögert gelesen werden, stattdessen einmal während der Interpreterinitialisierung gelesen werden. Mit der Weiterentwicklung der Referenzimplementierung werden diese im Einzelfall detaillierter besprochen. Die Umgebungsvariablen, von denen derzeit bekannt ist, dass sie dynamisch nachgeschlagen werden, sind
PYTHONCASEOK: Das Schreiben inos.environ['PYTHONCASEOK']wird die Behandlung von Groß-/Kleinschreibungsunterschieden bei der Dateinamenssuche während des Imports nicht mehr dynamisch ändern (TBC)PYTHONINSPECT:os.environ['PYTHONINSPECT']wird nach Abschluss der Ausführung des Moduls__main__immer noch geprüft.
Der Initialisierungsstil Py_Initialize() wird weiterhin unterstützt. Er wird intern (zumindest einige Elemente) der neuen API verwenden, aber weiterhin das gleiche Verhalten wie heute zeigen, um sicherzustellen, dass sys.argv erst bei einem nachfolgenden PySys_SetArgv-Aufruf befüllt wird (TBC). Alle APIs, die derzeit vor Py_Initialize() aufgerufen werden können, werden dies weiterhin tun und auch vor Py_InitializeRuntime() unterstützt.
Ein System-Python-Executable
Beim Ausführen von Systemdienstprogrammen mit Administratorzugriff auf ein System sind viele der Standardverhaltensweisen von CPython unerwünscht, da sie nicht vertrauenswürdigem Code die Ausführung mit erhöhten Rechten ermöglichen können. Die problematischsten Aspekte sind die Tatsache, dass Benutzer-Site-Verzeichnisse aktiviert sind, Umgebungsvariablen vertraut werden und das Verzeichnis, das die ausgeführte Datei enthält, am Anfang des Importpfads steht.
Die Ausgabe 16499 [6] fügte eine Option -I hinzu, um das Verhalten des normalen CPython-Ausführbaren zu ändern, aber dies ist eine schwer zu entdeckende Lösung (und fügt dem bereits komplexen CLI eine weitere Option hinzu). Diese PEP schlägt stattdessen die Hinzufügung eines separaten ausführbaren Programms namens system-python vor.
Derzeit wäre die Bereitstellung eines separaten ausführbaren Programms mit anderem Standardverhalten prohibitiv schwierig zu warten. Eines der Ziele dieser PEP ist es, die Möglichkeit zu schaffen, einen großen Teil des schwer zu wartenden Bootstrapping-Codes durch normaleren CPython-Code zu ersetzen, sowie es einer separaten Anwendung zu erleichtern, Schlüsselkomponenten von Py_Main zu nutzen. Die Einbeziehung dieser Änderung in die PEP soll dazu beitragen, die Akzeptanz eines Designs zu vermeiden, das in der Theorie gut klingt, sich aber in der Praxis als problematisch erweist.
Die saubere Unterstützung dieser Art von „alternativer CLI“ ist der Hauptgrund für die vorgeschlagenen Änderungen, um die Kernlogik für die Entscheidung zwischen den verschiedenen von CPython unterstützten Ausführungsmodi besser zugänglich zu machen
- Skriptausführung
- Verzeichnis-/Zipfile-Ausführung
- Befehlsausführung („-c“-Schalter)
- Modul- oder Paket-Ausführung („-m“-Schalter)
- Ausführung von stdin (nicht-interaktiv)
- interaktives stdin
Die tatsächliche Implementierung kann auch den Bedarf an einer besseren Infrastruktur für die Argumentenanalyse während der Initialisierungsphase aufzeigen.
Offene Fragen
- Fehlerdetails für
Py_BuildPythonConfigundPy_InitializeMainInterpreter(diese sollten mit fortschreitender Implementierung klarer werden)
Implementierung
Die Referenzimplementierung wird als private API-Refactoring innerhalb des CPython-Referenzinterpreters entwickelt (da der Versuch, sie als unabhängiges Projekt zu pflegen, sich als unpraktisch erwiesen hat).
PEP 587 extrahiert einen Teil des Vorschlags, der als ausreichend stabil gilt, um als öffentliche API für Python 3.8 vorgeschlagen zu werden.
Der Status Quo (Stand Python 3.6)
Die aktuellen Mechanismen zur Konfiguration des Interpreters haben sich im Laufe der letzten 20+ Jahre ziemlich ad hoc angesammelt, was zu einer eher inkonsistenten Schnittstelle mit unterschiedlichem Dokumentationsniveau geführt hat.
Siehe auch PEP 587 für weitere Diskussionen über die bestehenden Einstellungen und deren Handhabung.
(Hinweis: Einige der untenstehenden Informationen könnten wahrscheinlich bereinigt und der C-API-Dokumentation für 3.x hinzugefügt werden – es ist alles CPython-spezifisch und gehört nicht in die Sprachreferenz)
Ignorieren von Umgebungsvariablen
Die Kommandozeilenoption -E erlaubt es, alle Umgebungsvariablen bei der Initialisierung des Python-Interpreters zu ignorieren. Eine einbettende Anwendung kann dieses Verhalten aktivieren, indem sie Py_IgnoreEnvironmentFlag setzt, bevor sie Py_Initialize() aufruft.
Im CPython-Quellcode prüft das Makro Py_GETENV implizit dieses Flag und gibt NULL zurück, wenn es gesetzt ist.
<TBD: Ich glaube, PYTHONCASEOK wird unabhängig von dieser Einstellung geprüft> <TBD: Ignoriert -E auch Windows-Registrierungsschlüssel?>
Randomisierte Hashing
Die zufällige Hash-Erzeugung wird über die Kommandozeilenoption -R (in Versionen vor 3.3) sowie über die Umgebungsvariable PYTHONHASHSEED gesteuert.
In Python 3.3 ist nur noch die Umgebungsvariable relevant. Sie kann verwendet werden, um die zufällige Hash-Erzeugung zu deaktivieren (durch Verwendung eines Seed-Werts von 0) oder um einen spezifischen Hash-Wert zu erzwingen (z. B. für die Wiederholbarkeit von Tests oder um Hash-Werte zwischen Prozessen zu teilen).
Einbettende Anwendungen müssen jedoch Py_HashRandomizationFlag verwenden, um die Hash-Randomisierung explizit anzufordern (CPython setzt sie in Py_Main() und nicht in Py_Initialize()).
Die neue Konfigurations-API sollte es für eine einbettende Anwendung einfach machen, die Verarbeitung von PYTHONHASHSEED mit einer textbasierten Konfigurationseinstellung wiederzuverwenden, die auf andere Weise bereitgestellt wird (z. B. eine Konfigurationsdatei oder eine separate Umgebungsvariable).
Lokalisierung von Python und der Standardbibliothek
Der Speicherort der Python-Binärdatei und der Standardbibliothek wird von mehreren Elementen beeinflusst. Der Algorithmus zur Durchführung der Berechnung ist nirgendwo anders als im Quellcode dokumentiert [3], [4]. Selbst diese Beschreibung ist unvollständig, da sie für die in Python 3.3 hinzugefügte Unterstützung von virtuellen Umgebungen (detailliert in PEP 405) nicht aktualisiert wurde.
Diese Berechnungen werden von den folgenden Funktionsaufrufen (vor dem Aufruf von Py_Initialize()) und Umgebungsvariablen beeinflusst
Py_SetProgramName()Py_SetPythonHome()PYTHONHOME
Das Dateisystem wird auch auf pyvenv.cfg-Dateien (siehe PEP 405) oder, falls dies fehlschlägt, auf eine lib/os.py (Windows) oder lib/python$VERSION/os.py-Datei geprüft.
Die Build-Zeit-Einstellungen für PREFIX und EXEC_PREFIX sind ebenfalls relevant, ebenso wie einige Registrierungseinstellungen unter Windows. Die fest codierten Fallbacks basieren auf dem Layout des CPython-Quellcode-Trees und der Build-Ausgabe bei der Arbeit in einem Quellcode-Checkout.
Konfiguration von sys.path
Eine einbettende Anwendung kann Py_SetPath() vor Py_Initialize() aufrufen, um die Berechnung von sys.path vollständig zu überschreiben. Es ist nicht einfach, nur *einige* der Berechnungen zuzulassen, da Modifikationen an sys.path nach Abschluss der Initialisierung dazu führen, dass diese Modifikationen nicht wirksam sind, wenn Standardbibliotheksmodule während der Startsequenz importiert werden.
Wenn Py_SetPath() nicht vor dem ersten Aufruf von Py_GetPath() (implizit in Py_Initialize()) verwendet wird, baut es auf den obigen Berechnungen der Standortdaten auf, um geeignete Pfadeinträge zu berechnen, zusammen mit der Umgebungsvariable PYTHONPATH.
<TBD: Unter Windows gibt es auch eine ganze Menge mit der Registrierung>
Das Modul site, das beim Start implizit importiert wird (sofern nicht über die Option -S deaktiviert), fügt dieser ursprünglichen Sammlung von Pfaden zusätzliche Pfade hinzu, wie in seiner Dokumentation beschrieben [5].
Die Kommandozeilenoption -s kann verwendet werden, um das Benutzer-Site-Verzeichnis aus der Liste der hinzugefügten Verzeichnisse auszuschließen. Einbettende Anwendungen können dies durch Setzen der globalen Variable Py_NoUserSiteDirectory steuern.
Die folgenden Befehle können verwendet werden, um die Standardpfadkonfigurationen für eine gegebene Python-Ausführungsdatei auf einem gegebenen System zu überprüfen
./python -c "import sys, pprint; pprint.pprint(sys.path)"- Standardkonfiguration./python -s -c "import sys, pprint; pprint.pprint(sys.path)"- Benutzer-Site-Verzeichnis deaktiviert./python -S -c "import sys, pprint; pprint.pprint(sys.path)"- Alle Modifikationen des Site-Pfads deaktiviert
(Hinweis: Sie können ähnliche Informationen sehen, indem Sie -m site anstelle von -c verwenden, aber dies ist leicht irreführend, da es os.path.abspath auf alle Pfadeinträge anwendet, wodurch relative Pfadeinträge absolut erscheinen. Die Verwendung des Moduls site verursacht auch Probleme im letzten Fall, da bei Python-Versionen vor 3.3 der explizite Import von site die Pfadmodifikationen vornimmt, die -S vermeidet, während bei 3.3+ die Kombination von -m site mit -S derzeit fehlschlägt)
Die Berechnung von sys.path[0] ist vergleichsweise unkompliziert
- Für ein gewöhnliches Skript (Python-Quelle oder kompilierter Bytecode) ist
sys.path[0]das Verzeichnis, das das Skript enthält. - Für einen gültigen
sys.path-Eintrag (typischerweise eine Zip-Datei oder ein Verzeichnis) istsys.path[0]dieser Pfad - Für eine interaktive Sitzung, die von stdin ausgeführt wird oder wenn die Schalter
-coder-mverwendet werden, istsys.path[0]die leere Zeichenkette, die das Importsystem so interpretiert, dass Importe aus dem aktuellen Verzeichnis erlaubt sind
Konfiguration von sys.argv
Im Gegensatz zu den meisten anderen Einstellungen, die in diesem PEP behandelt werden, wird sys.argv nicht implizit von Py_Initialize() gesetzt. Stattdessen muss es durch einen expliziten Aufruf von Py_SetArgv() gesetzt werden.
CPython ruft dies in Py_Main() auf, nachdem Py_Initialize() aufgerufen wurde. Die Berechnung von sys.argv[1:] ist unkompliziert: Sie sind die Kommandozeilenargumente, die nach dem Skriptnamen oder dem Argument für die Optionen -c oder -m übergeben werden.
Die Berechnung von sys.argv[0] ist etwas komplizierter
- Für ein gewöhnliches Skript (Quelle oder Bytecode) ist es der Skriptname
- Für einen
sys.path-Eintrag (typischerweise eine Zip-Datei oder ein Verzeichnis) ist es zunächst der Name der Zip-Datei oder des Verzeichnisses, wird aber später vom Modulrunpyauf den vollständigen Pfad zum importierten__main__-Modul geändert. - Für ein Modul, das mit dem Schalter
-mangegeben wird, ist es zunächst die Zeichenkette"-m", wird aber später vom Modulrunpyauf den vollständigen Pfad zum ausgeführten Modul geändert. - Für ein Paket, das mit dem Schalter
-mangegeben wird, ist es zunächst die Zeichenkette"-m", wird aber später vom Modulrunpyauf den vollständigen Pfad zum ausgeführten__main__-Untermodul des Pakets geändert. - Für einen Befehl, der mit
-causgeführt wird, ist es die Zeichenkette"-c" - Für explizit angeforderte Eingaben von stdin ist es die Zeichenkette
"-" - Andernfalls ist es die leere Zeichenkette
Einbettende Anwendungen müssen Py_SetArgv selbst aufrufen. Die CPython-Logik dafür ist Teil von Py_Main() und wird nicht separat verfügbar gemacht. Das Modul runpy bietet jedoch eine annähernd äquivalente Logik in runpy.run_module und runpy.run_path.
Weitere Konfigurationseinstellungen
TBD: Behandeln Sie die Initialisierung der folgenden Punkte detaillierter
- Das Importsystem vollständig deaktivieren
- Der anfängliche Zustand des Warnsystems
sys.warnoptions- (-W Option, PYTHONWARNINGS)
- Beliebige erweiterte Optionen (z. B. um
faulthandlerautomatisch zu aktivieren)sys._xoptions- (-X Option)
- Die Dateisystemkodierung, die verwendet wird von
sys.getfsencodingos.fsencodeos.fsdecode
- Die IO-Kodierung und Pufferung, die verwendet wird von
sys.stdinsys.stdoutsys.stderr- (-u Option, PYTHONIOENCODING, PYTHONUNBUFFEREDIO)
- Ob Bytecode-Dateien implizit gecacht werden sollen oder nicht
sys.dont_write_bytecode- (-B Option, PYTHONDONTWRITEBYTECODE)
- Ob auf Dateinamen auf plattformen mit nicht fallbasierter Groß-/Kleinschreibung korrekte Groß-/Kleinschreibung erzwungen werden soll oder nicht
os.environ["PYTHONCASEOK"]
- Die anderen Einstellungen, die für Python-Code in
sys.flagsverfügbar sinddebug(Debugging-Ausgabe im pgen-Parser aktivieren)inspect(Interaktiven Interpreter nach Beendigung von __main__ aufrufen)interactive(Stdin als TTY behandeln)optimize(__debug__ Status, .pyc oder .pyo schreiben, Docstrings entfernen)no_user_site(Benutzer-Site-Verzeichnis nicht zu sys.path hinzufügen)no_site(site nicht implizit beim Start importieren)ignore_environment(ob Umgebungsvariablen während der Konfiguration verwendet werden)verbose(alle Arten von zufälligen Ausgaben aktivieren)bytes_warning(Warnungen/Fehler für implizite str/bytes-Interaktion)quiet(Banner-Ausgabe deaktivieren, auch wenn verbose ebenfalls aktiviert ist oder stdin eine TTY ist und der Interpreter im interaktiven Modus gestartet wird)
- Ob die Signal-Handler von CPython installiert werden sollen oder nicht
Ein großer Teil der Konfiguration von CPython wird derzeit über globale C-Variablen gehandhabt
Py_BytesWarningFlag (-b)
Py_DebugFlag (-d option)
Py_InspectFlag (-i option, PYTHONINSPECT)
Py_InteractiveFlag (property of stdin, cannot be overridden)
Py_OptimizeFlag (-O option, PYTHONOPTIMIZE)
Py_DontWriteBytecodeFlag (-B option, PYTHONDONTWRITEBYTECODE)
Py_NoUserSiteDirectory (-s option, PYTHONNOUSERSITE)
Py_NoSiteFlag (-S option)
Py_UnbufferedStdioFlag (-u, PYTHONUNBUFFEREDIO)
Py_VerboseFlag (-v option, PYTHONVERBOSE)
Für die oben genannten Variablen wird die Konvertierung von Kommandozeilenoptionen und Umgebungsvariablen in globale C-Variablen von Py_Main gehandhabt, sodass jede einbettende Anwendung diese entsprechend setzen muss, um sie von ihren Standardwerten abzuweichen.
Einige Konfigurationen können nur als OS-Level-Umgebungsvariablen bereitgestellt werden
PYTHONSTARTUP
PYTHONCASEOK
PYTHONIOENCODING
Die Py_InitializeEx() API akzeptiert ebenfalls ein boolesches Flag, um anzugeben, ob die Signal-Handler von CPython installiert werden sollen oder nicht.
Schließlich werden einige interaktive Verhaltensweisen (wie das Drucken des einführenden Banners) nur ausgelöst, wenn die Standardeingabe vom Betriebssystem als Terminalverbindung gemeldet wird.
TBD: Dokumentieren Sie, wie die Option „-x“ behandelt wird (überspringt die Verarbeitung der ersten Kommentarzeile im Hauptskript)
Siehe auch detaillierte Ablauffolge-Notizen unter [1].
Referenzen
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0432.rst
Zuletzt geändert: 2025-02-01 08:59:27 GMT