PEP 499 – python -m foo sollte auch 'foo' in sys.modules binden
- Autor:
- Cameron Simpson <cs at cskk.id.au>, Chris Angelico <rosuav at gmail.com>, Joseph Jevnik <joejev at gmail.com>
- BDFL-Delegate:
- Alyssa Coghlan
- Status:
- Verschoben
- Typ:
- Standards Track
- Erstellt:
- 07-Aug-2015
- Python-Version:
- 3.10
PEP Verschiebung
Die Implementierung dieses PEP wird voraussichtlich nicht rechtzeitig zum Python 3.9 Feature Freeze im April 2020 fertig sein, daher wurde sie um 12 Monate auf Python 3.10 verschoben.
Zusammenfassung
Wenn ein Modul auf der Python-Kommandozeile als Hauptprogramm verwendet wird, z. B. mit
python -m module.name …
ist es leicht, versehentlich zwei unabhängige Instanzen des Moduls zu erhalten, wenn dieses Modul innerhalb des Programms erneut importiert wird. Dieses PEP schlägt eine Möglichkeit vor, dieses Problem zu beheben.
Wenn ein Modul über die -m-Option von Python aufgerufen wird, wird das Modul an sys.modules['__main__'] gebunden und sein .__name__-Attribut wird auf '__main__' gesetzt. Dies ermöglicht den Standard-Boilerplate-Code für "Hauptprogramme" am Ende vieler Module, wie z. B.:
if __name__ == '__main__':
sys.exit(main(sys.argv))
Wenn jedoch die obige Kommandozeilenaufforderung verwendet wird, liegt die natürliche Schlussfolgerung nahe, dass das Modul tatsächlich unter seinem offiziellen Namen module.name importiert wird, und dass daher, wenn das Programm diesen Namen erneut importiert, dieselbe Modulinstanz erhalten wird.
Tatsächlich wurde das Modul nur als '__main__' importiert. Ein weiterer Import erhält eine separate Modulinstanz, was zu verwirrenden Fehlern führen kann, die alle von zwei Instanzen von Modul-Globalen Objekten herrühren: eine in jedem Modul.
Beispiele hierfür sind
- Datenstrukturen auf Modulebene
- Einige Module stellen Funktionen wie Caches oder Registries als globale Variablen auf Modulebene bereit, typischerweise privat. Eine zweite Instanz eines Moduls erstellt eine zweite Datenstruktur. Wenn diese Struktur ein Cache ist, wie im
re-Modul, existieren zwei Caches, was zu verschwenderischer Speichernutzung führt. Wenn diese Struktur eine gemeinsam genutzte Registry ist, wie eine Zuordnung von Werten zu Handlern, ist es möglich, einen Handler zu einer Registry zu registrieren und zu versuchen, ihn über die andere Registry zu verwenden, wo er unbekannt ist. - Sentinel-Werte
- Der Standardtest für einen Sentinel-Wert, der von einem Modul bereitgestellt wird, ist der Identitätsvergleich mit
is, da dies unzuverlässige "sieht aus wie"-Vergleiche vermeidet, wie z. B. Gleichheit, die sowohl zwei Werte als "gleich" falsch einstufen können (z. B. nullähnlich sein) als auch einenTypeErrorauslösen können, wenn die Objekte inkompatibel sind. Wenn es zwei Instanzen eines Moduls gibt, gibt es zwei Sentinel-Instanzen und nur eine wird überiserkannt. - Klassen
- Bei zwei Modulen gibt es doppelte Klassendefinitionen aller bereitgestellten Klassen. Alle Operationen, die von der Erkennung dieser Klassen und Unterklassen abhängen, sind fehleranfällig, je nachdem, wo die Referenzklasse (aus einem der Module) bezogen und wo die Vergleichsklasse oder Instanz bezogen wird. Dies betrifft
isinstance,issubclassund auchtry/except-Konstrukte.
Vorschlag
Es wird vorgeschlagen, dass zur Behebung dieser Situation eine einfache Änderung der Implementierung der -m-Option ausreicht: Zusätzlich zur Bindung des Modulobjekts an sys.modules['__main__'] wird es auch an sys.modules['module.name'] gebunden.
Alyssa (Nick) Coghlan hat vorgeschlagen, dass dies so einfach ist wie die Modifikation der Funktion _run_module_as_main im Modul runpy wie folgt:
main_globals = sys.modules["__main__"].__dict__
anstatt zu sein
main_module = sys.modules["__main__"]
sys.modules[mod_spec.name] = main_module
main_globals = main_module.__dict__
Joseph Jevnik hat darauf hingewiesen, dass Module, die Pakete sind, bereits etwas sehr Ähnliches zu diesem Vorschlag tun: Die Datei __init__.py wird an den kanonischen Namen des Moduls gebunden und die Datei __main__.py wird an "__main__" gebunden. Daher tritt das Problem des doppelten Imports nicht auf. Daher schlägt dieses PEP vor, nur einfache Nicht-Paket-Module zu beeinflussen.
Überlegungen und Voraussetzungen
Module pickeln
Alyssa hat Issue 19702 erwähnt, das (zitiert aus dem Issue) vorschlägt:
- runpy wird sicherstellen, dass, wenn __main__ über das Importsystem ausgeführt wird, es auch in sys.modules als __spec__.name aliasiert wird.
- Wenn __main__.__spec__ gesetzt ist, wird pickle __spec__.name anstelle von __name__ verwenden, um Klassen, Funktionen und Methoden zu pickeln, die in __main__ definiert sind.
- multiprocessing wird entsprechend aktualisiert, um die Erstellung von __mp_main__ in Kindprozessen zu überspringen, wenn __main__.__spec__ im Elternprozess gesetzt ist.
Der erste Punkt oben deckt den spezifischen Vorschlag dieses PEP ab.
Der __name__ eines normalen Moduls ist nicht mehr kanonisch
Chris Angelico weist darauf hin, dass es möglich wird, ein Modul zu importieren, dessen __name__ nicht mit dem übereinstimmt, was Sie für "import" angegeben haben, da "__main__" nun unter "module.name" vorhanden ist, sodass ein nachfolgender import module.name es bereits vorhanden findet. Daher ist __name__ für einige normale Importe nicht mehr der kanonische Name.
Einige Gegenargumente folgen
- Gemäß PEP 451 wird der kanonische Name eines Moduls unter
__spec__.namegespeichert. - Sehr wenig Code sollte sich tatsächlich um
__name__als kanonischen Namen kümmern, und jeder, der dies tut, sollte argumentativ aktualisiert werden, um__spec__.namezu konsultieren, mit einem Fallback auf__name__für ältere Pythons, falls dies relevant ist. Dies gilt auch dann, wenn dieses PEP nicht genehmigt wird. - Wenn dieses PEP genehmigt wird, wird es möglich, ein Modul anhand seines kanonischen Namens zu introspezieren und zu fragen: "War dies das Hauptprogramm?", indem man von
__name__ableitet. Dies war bisher nicht möglich.
Das offensichtliche Gegenbeispiel ist die Standard-Boilerplate "bin ich das Hauptprogramm?", bei der erwartet wird, dass __name__ "__main__" ist. Dieses PEP bewahrt diese Semantik ausdrücklich.
Referenzimplementierung
BPO 36375 ist der Eintrag im Issue-Tracker für die Referenzimplementierung des PEP, der aktuelle Entwurf für einen PR ist auf GitHub verfügbar.
Offene Fragen
Dieser Vorschlag wirft einige Bedenken hinsichtlich der Abwärtskompatibilität auf, die gut verstanden werden müssen, und entweder ein Deprozess entworfen oder klare Portierungsrichtlinien bereitgestellt werden müssen.
Pickle-Kompatibilität
Wenn keine Änderungen am Pickle-Modul vorgenommen werden, können Pickles, die zuvor mit dem korrekten Modulnamen geschrieben wurden (aufgrund eines doppelten Imports), stattdessen mit __main__ als Modulnamen geschrieben werden und somit von anderen Projekten nicht korrekt geladen werden.
Zu prüfende Szenarien
python script.pyschreibt,python -m scriptliestpython -m scriptschreibt,python script.pyliestpython -m scriptschreibt,python some_other_app.pyliestold_python -m scriptschreibt,new_python -m scriptliestnew_python -m scriptschreibt,old_python -m scriptliest
Projekte, die __main__ speziell behandeln
Um die Regressionstest-Suite zum Bestehen zu bringen, musste die aktuelle Referenzimplementierung pdb patchen, um die Zerstörung seines eigenen globalen Namensraums zu vermeiden.
Dies deutet darauf hin, dass es ein breiteres Kompatibilitätsproblem geben könnte, bei dem einige Skripte sich auf direkte Ausführung und Import verlassen, die unterschiedliche Namensräume ergeben (genauso wie die Ausführung von Paketen die beiden trennt, indem das __main__-Untermodul im __main__-Namensraum ausgeführt wird, während der Paketname wie üblich auf die Datei __init__ verweist.
Hintergrund
Ich bin über dieses Problem gestolpert, als ich ein Hauptprogramm über ein Modul debuggte, das versuchte, ein benanntes Modul zu "monkey-patchen", welches das Hauptprogramm-Modul war. Natürlich war das Monkey-Patching unwirksam, da es das Hauptmodul namentlich importierte und somit die zweite Modulinstanz patchte, nicht die laufende Modulinstanz.
Das Problem besteht jedoch, seit es die -m-Kommandozeilenoption gibt und wird regelmäßig, wenn auch selten, von anderen angetroffen.
Zusätzlich zu Issue 19702 wird die Diskrepanz um __main__ in PEP 451 angedeutet und ein ähnlicher Vorschlag (vor PEP 451) wird in PEP 395 unter Fixing dual imports of the main module beschrieben.
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0499.rst
Zuletzt geändert: 2025-02-01 08:55:40 GMT