PEP 338 – Module als Skripte ausführen
- Autor:
- Alyssa Coghlan <ncoghlan at gmail.com>
- Status:
- Final
- Typ:
- Standards Track
- Erstellt:
- 16. Okt. 2004
- Python-Version:
- 2.5
- Post-History:
- 08. Nov. 2004, 11. Feb. 2006, 12. Feb. 2006, 18. Feb. 2006
Zusammenfassung
Diese PEP definiert die Semantik für die Ausführung jedes Python-Moduls als Skript, entweder mit dem -m Kommandozeilenschalter oder durch Aufruf über runpy.run_module(modulename).
Der in Python 2.4 implementierte -m Schalter ist ziemlich begrenzt. Diese PEP schlägt die Verwendung der PEP 302 Import-Hooks vor, um jedes Modul ausführen zu können, das Zugriff auf sein Code-Objekt bietet.
Begründung
Python 2.4 fügt den Kommandozeilenschalter -m hinzu, um Module über den Python-Modul-Namensraum für die Ausführung als Skripte lokalisieren zu können. Die motivierenden Beispiele waren Standardbibliotheksmodule wie pdb und profile, und die Python 2.4-Implementierung ist für diesen begrenzten Zweck in Ordnung.
Eine Reihe von Benutzern und Entwicklern hat eine Erweiterung der Funktion gefordert, um auch das Ausführen von Modulen innerhalb von Paketen zu unterstützen. Ein Beispiel ist das pychecker.checker Modul von pychecker. Diese Funktionalität wurde aus der Python 2.4-Implementierung weggelassen, da deren Implementierung deutlich komplizierter war und die am besten geeignete Strategie nicht klar war.
Die Meinung auf python-dev war, dass es besser sei, die Erweiterung auf Python 2.5 zu verschieben und den PEP-Prozess zu durchlaufen, um sicherzustellen, dass wir es richtig machen.
Seitdem wurde auch darauf hingewiesen, dass die aktuelle Version von -m zipimport oder andere Arten von alternativen Importverhalten (wie z. B. gefrorene Module) nicht unterstützt.
Die Bereitstellung dieser Funktionalität als Python-Modul ist deutlich einfacher als die Implementierung in C und macht die Funktionalität allen Python-Programmen leicht zugänglich, anstatt nur dem CPython-Interpreter spezifisch zu sein. Der Kommandozeilenschalter von CPython kann dann überarbeitet werden, um das neue Modul zu nutzen.
Skripte, die andere Skripte ausführen (z. B. profile, pdb), haben auch die Option, das neue Modul zu verwenden, um eine -m ähnliche Unterstützung für die Identifizierung des auszuführenden Skripts bereitzustellen.
Umfang dieser Proposal
In Python 2.4 wird ein Modul, das mit -m gefunden wurde, genauso ausgeführt, als ob sein Dateiname auf der Kommandozeile angegeben worden wäre. Das Ziel dieser PEP ist es, diese Aussage so weit wie möglich auf Module innerhalb von Paketen oder solche, die über alternative Importmechanismen (wie z. B. zipimport) zugänglich sind, auszudehnen.
Frühere Diskussionen legen nahe, dass es zu beachten gilt, dass diese PEP nicht dazu dient, das Idiom zu ändern, um Python-Module auch als Skripte nutzbar zu machen (siehe PEP 299). Dieses Problem wird als orthogonal zu der spezifischen Funktionalität betrachtet, die von dieser PEP behandelt wird.
Aktuelles Verhalten
Bevor die neuen Semantiken beschrieben werden, ist es sinnvoll, die bestehenden Semantiken für Python 2.4 zu behandeln (da sie derzeit nur durch den Quellcode und die Kommandozeilenhilfe definiert sind).
Wenn -m auf der Kommandozeile verwendet wird, beendet es sofort die Optionsliste (wie -c). Das Argument wird als Name eines Top-Level-Python-Moduls interpretiert (d. h. eines, das in sys.path gefunden werden kann).
Wenn das Modul gefunden wird und vom Typ PY_SOURCE oder PY_COMPILED ist, wird die Kommandozeile effektiv von python <options> -m <module> <args> zu python <options> <filename> <args> neu interpretiert. Dies beinhaltet die korrekte Einstellung von sys.argv[0] (einige Skripte sind darauf angewiesen – Pythons eigenes regrtest.py ist ein Beispiel).
Wenn das Modul nicht gefunden wird oder nicht vom richtigen Typ ist, wird eine Fehlermeldung ausgegeben.
Proposed Semantics
Die vorgeschlagenen Semantiken sind recht einfach: Wenn -m zum Ausführen eines Moduls verwendet wird, werden die PEP 302 Import-Mechanismen verwendet, um das Modul zu lokalisieren und seinen kompilierten Code abzurufen, bevor das Modul gemäß den Semantiken eines Top-Level-Moduls ausgeführt wird. Der Interpreter tut dies durch Aufruf einer neuen Standardbibliotheksfunktion runpy.run_module.
Dies ist aufgrund der Art und Weise, wie Pythons Import-Maschinerie Module innerhalb von Paketen lokalisiert, notwendig. Ein Paket kann seine eigene __path__ Variable während der Initialisierung ändern. Darüber hinaus können Pfade durch *.pth Dateien beeinflusst werden, und einige Pakete installieren benutzerdefinierte Loader auf sys.metapath. Daher ist der einzige Weg für Python, das Modul zuverlässig zu lokalisieren, das Importieren des enthaltenden Pakets und die Verwendung der PEP 302 Import-Hooks, um Zugriff auf den Python-Code zu erhalten.
Beachten Sie, dass der Prozess der Lokalisierung des auszuführenden Moduls möglicherweise den Import des enthaltenden Pakets erfordert. Die Auswirkungen eines solchen Paketimports, die für das ausgeführte Modul sichtbar sind, sind:
- Das enthaltende Paket wird in sys.modules sein
- Jegliche externen Auswirkungen der Paketinitialisierung (z. B. installierte Import-Hooks, Logger, atexit-Handler usw.)
Referenzimplementierung
Eine Referenzimplementierung ist auf SourceForge verfügbar ([2]), zusammen mit Dokumentation für die Bibliotheksreferenz ([5]). Diese Implementierung besteht aus zwei Teilen. Der erste ist ein vorgeschlagenes Standardbibliotheksmodul runpy. Der zweite ist eine Änderung am Code, der den -m Schalter implementiert, um immer an runpy.run_module zu delegieren, anstatt zu versuchen, das Modul direkt auszuführen. Die Delegation hat die Form
runpy.run_module(sys.argv[0], run_name="__main__", alter_sys=True)
run_module ist die einzige Funktion, die runpy in seiner öffentlichen API exponiert.
run_module(mod_name[, init_globals][, run_name][, alter_sys])
Führt den Code des angegebenen Moduls aus und gibt das resultierende Modul-Globals-Dictionary zurück. Der Code des Moduls wird zuerst mit dem Standard-Importmechanismus lokalisiert (siehe PEP 302 für Details) und dann in einem frischen Modul-Namensraum ausgeführt.Das optionale Dictionary-Argument
init_globalskann verwendet werden, um das Globals-Dictionary vor der Ausführung des Codes vorab zu befüllen. Das übergebene Dictionary wird nicht geändert. Wenn eine der unten aufgeführten speziellen globalen Variablen im übergebenen Dictionary definiert ist, werden diese Definitionen von der Funktion run_module überschrieben.Die speziellen globalen Variablen
__name__,__file__,__loader__und__builtins__werden im Globals-Dictionary gesetzt, bevor der Modulcode ausgeführt wird.
__name__wird aufrun_namegesetzt, wenn dieses optionale Argument übergeben wird, andernfalls auf das ursprünglichemod_nameArgument.
__loader__wird auf den PEP 302 Modul-Loader gesetzt, der zum Abrufen des Codes für das Modul verwendet wird (Dieser Loader kann ein Wrapper um den Standard-Importmechanismus sein).
__file__wird auf den vom Modul-Loader bereitgestellten Namen gesetzt. Wenn der Loader keine Dateiname-Informationen zur Verfügung stellt, wird dieses Argument aufNonegesetzt.
__builtins__wird automatisch mit einer Referenz auf den Top-Level-Namensraum des__builtin__Moduls initialisiert.Wenn das Argument
alter_sysübergeben wird und zuTrueausgewertet wird, wirdsys.argv[0]mit dem Wert von__file__aktualisiert undsys.modules[__name__]mit einem temporären Modulobjekt für das ausgeführte Modul aktualisiert. Sowohlsys.argv[0]als auchsys.modules[__name__]werden vor Rückgabe der Funktion auf ihre ursprünglichen Werte zurückgesetzt.
Wenn es als Skript aufgerufen wird, findet das Modul runpy das als erstes Argument übergebene Modul und führt es aus. Es passt sys.argv an, indem es sys.argv[0] (das sich auf das runpy Modul selbst bezieht) löscht und ruft dann run_module(sys.argv[0], run_name="__main__", alter_sys=True) auf.
Import-Anweisungen und das Hauptmodul
Die Veröffentlichung von 2.5b1 zeigte eine überraschende (wenn auch im Nachhinein offensichtliche) Wechselwirkung zwischen dieser PEP und PEP 328 - explizite relative Imports funktionieren nicht von einem Hauptmodul aus. Dies liegt daran, dass relative Imports __name__ verwenden, um die Position des aktuellen Moduls in der Paket-Hierarchie zu bestimmen. In einem Hauptmodul ist der Wert von __name__ immer '__main__', daher schlagen explizite relative Imports immer fehl (da sie nur für ein Modul innerhalb eines Pakets funktionieren).
Die Untersuchung, warum implizite relative Imports *scheinbar* funktionieren, wenn ein Hauptmodul direkt ausgeführt wird, aber fehlschlagen, wenn es mit -m ausgeführt wird, zeigte, dass solche Imports tatsächlich immer als absolute Imports behandelt werden. Aufgrund der Art und Weise, wie die direkte Ausführung funktioniert, wird das Paket, das das ausgeführte Modul enthält, zu sys.path hinzugefügt, so dass seine Geschwistermodule tatsächlich als Top-Level-Module importiert werden. Dies kann leicht zu mehreren Kopien der Geschwistermodule in der Anwendung führen, wenn implizite relative Imports in Modulen verwendet werden, die direkt ausgeführt werden können (z. B. Testmodule oder Utility-Skripte).
Für die Veröffentlichung von 2.5 wird empfohlen, in jedem Modul, das als Hauptmodul verwendet werden soll, immer absolute Imports zu verwenden. Der -m Schalter bietet hier einen Vorteil, da er das aktuelle Verzeichnis in sys.path einfügt, anstatt das Verzeichnis, das das Hauptmodul enthält. Das bedeutet, dass es möglich ist, ein Modul aus einem Paket heraus mit -m auszuführen, solange das aktuelle Verzeichnis das Top-Level-Verzeichnis des Pakets enthält. Absolute Imports funktionieren korrekt, auch wenn das Paket nicht woanders in sys.path installiert ist. Wenn das Modul direkt ausgeführt wird und absolute Imports verwendet, um seine Geschwistermodule abzurufen, muss das Top-Level-Paketverzeichnis irgendwo in sys.path installiert sein (da das aktuelle Verzeichnis nicht automatisch hinzugefügt wird).
Hier ist ein Beispiel für die Dateistruktur
devel/
pkg/
__init__.py
moduleA.py
moduleB.py
test/
__init__.py
test_A.py
test_B.py
Solange das aktuelle Verzeichnis devel ist oder devel bereits in sys.path vorhanden ist und die Testmodule absolute Imports verwenden (z. B. import pkg moduleA, um das zu testende Modul abzurufen), erlaubt PEP 338, die Tests als auszuführen
python -m pkg.test.test_A
python -m pkg.test.test_B
Die Frage, ob relative Imports unterstützt werden sollten, wenn ein Hauptmodul mit -m ausgeführt wird, wird für Python 2.6 erneut geprüft. Dies zu erlauben würde Änderungen entweder an den Import-Semantiken von Python oder an den Semantiken, die zur Anzeige verwenden, wann ein Modul das Hauptmodul ist, erfordern, daher ist dies keine Entscheidung, die überstürzt getroffen werden sollte.
Gelöste Probleme
Es gab einige wichtige Designentscheidungen, die die Entwicklung des runpy Moduls beeinflusst haben. Diese sind unten aufgeführt.
- Die speziellen Variablen
__name__,__file__und__loader__werden im globalen Namensraum eines Moduls gesetzt, bevor das Modul ausgeführt wird. Darun_modulediese Werte ändert, mutiert es das übergebene Dictionary nicht. Wenn es dies tun würde, könnte die Übergabe vonglobals()an diese Funktion böse Nebenwirkungen haben. - Manchmal sind die Informationen, die zur Befüllung der speziellen Variablen benötigt werden, einfach nicht verfügbar. Anstatt zu versuchen, zu clever zu sein, werden diese Variablen einfach auf
Nonegesetzt, wenn die relevanten Informationen nicht ermittelt werden können. - Es gibt keinen besonderen Schutz für das `alter_sys`-Argument. Dies kann dazu führen, dass
sys.argv[0]aufNonegesetzt wird, wenn Dateiname-Informationen nicht verfügbar sind. - Die Import-Sperre wird NICHT verwendet, um potenzielle Threading-Probleme zu vermeiden, die auftreten, wenn `alter_sys` auf True gesetzt ist. Stattdessen wird empfohlen, dass gethreadeter Code einfach die Verwendung dieser Flagge vermeidet.
Alternativen
Die erste in Betracht gezogene alternative Implementierung ignorierte die __path__ Variablen von Paketen und suchte nur im Hauptpaketverzeichnis. Ein Python-Skript mit diesem Verhalten finden Sie in der Diskussion des execmodule Cookbook-Rezepts [3].
Das execmodule Cookbook-Rezept selbst war der vorgeschlagene Mechanismus in einer früheren Version dieser PEP (bevor der Autor der PEP PEP 302 las).
Beide Ansätze wurden abgelehnt, da sie nicht das Hauptziel des -m Schalters erfüllen – die Nutzung des vollständigen Python-Namensraums zur Lokalisierung von Modulen für die Ausführung von der Kommandozeile aus zu ermöglichen.
Eine frühere Version dieser PEP enthielt einige fehlerhafte Annahmen darüber, wie exec mit Locals-Dictionaries und Code aus Funktions-Objekten umging. Diese fehlerhaften Annahmen führten zu einigen unnötigen Designkomplexitäten, die nun entfernt wurden – run_code teilt alle Eigenheiten von exec.
Frühere Versionen der PEP legten auch eine breitere API offen als nur die einzelne run_module() Funktion, die zur Implementierung der Updates für den -m Schalter benötigt wurde. Im Interesse der Einfachheit wurden diese zusätzlichen Funktionen aus der vorgeschlagenen API gestrichen.
Nach der ursprünglichen Implementierung in SVN wurde deutlich, dass das Halten der Import-Sperre bei der Ausführung des ersten Anwendungsskripts nicht korrekt war (z. B. schlug python -m test.regrtest test_threadedimport fehl). Daher hält die Funktion run_module die Import-Sperre nur während der eigentlichen Suche nach dem Modul und gibt sie vor der Ausführung frei, auch wenn alter_sys gesetzt ist.
Referenzen
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0338.rst
Zuletzt geändert: 2025-02-01 08:59:27 GMT