5. Das Importsystem¶
Python-Code in einem Modul erhält Zugriff auf den Code in einem anderen Modul, indem er diesen importiert. Die Anweisung import ist die gängigste Methode, um den Importvorgang auszulösen, aber es ist nicht die einzige Möglichkeit. Auch Funktionen wie ` importlib.import_module() und die integrierte Funktion __import__() können verwendet werden, um den Importvorgang auszulösen.
Die Anweisung import kombiniert zwei Operationen: Sie sucht nach dem genannten Modul und bindet anschließend die Ergebnisse dieser Suche an einen Namen im lokalen Geltungsbereich. Die Suchoperation der Anweisung „ import ist als Aufruf der Funktion __import__() mit den entsprechenden Argumenten definiert. Der Rückgabewert von __import__() wird verwendet, um die Namensbindung der Anweisung import durchzuführen. Genaue Einzelheiten zu dieser Namensbindung finden Sie in der Anweisung import .
Ein direkter Aufruf von __import__() führt lediglich die Modulsuche durch und, falls das Modul gefunden wird, die Modulerstellung. Zwar können dabei gewisse Nebeneffekte auftreten, wie beispielsweise das Importieren übergeordneter Pakete und die Aktualisierung verschiedener Caches (einschließlich sys.modules), doch nur die Anweisung import führt eine Namensbindung durch.
Bei der Ausführung einer Anweisung vom Typ import wird die standardmäßige integrierte Funktion __import__() aufgerufen. Andere Mechanismen zum Aufruf des Importsystems (wie beispielsweise importlib.import_module() ) können __import__() umgehen und stattdessen eigene Lösungen zur Implementierung der Import-Semantik verwenden.
Wenn ein Modul zum ersten Mal importiert wird, sucht Python nach dem Modul und erstellt, falls es gefunden wird, ein Modulobjekt [1] und initialisiert es. Kann das genannte Modul nicht gefunden werden, wird eine Ausnahme vom Typ ModuleNotFoundError ausgelöst. Python setzt verschiedene Strategien ein, um nach dem genannten Modul zu suchen, wenn der Importmechanismus aufgerufen wird. Diese Strategien können mithilfe verschiedener Hooks, die in den folgenden Abschnitten beschrieben werden, angepasst und erweitert werden.
Geändert in Version 3.3: Das Import-System wurde aktualisiert, um die zweite Phase von PEP 302 vollständig umzusetzen. Es gibt keine implizite Import-Mechanik mehr – das gesamte Import-System wird über sys.meta_path bereitgestellt. Darüber hinaus wurde die native Unterstützung für Namespace-Pakete implementiert (siehe PEP 420).
5.1. importlib¶
Das Modul importlib bietet eine umfangreiche API für die Interaktion mit dem Import-System. So stellt beispielsweise importlib.import_module() eine empfohlene, einfachere API zum Aufrufen der Import-Funktionen dar als die integrierte Funktion __import__() “. Weitere Informationen finden Sie in der Dokumentation zur Bibliothek importlib .
5.2. Pakete¶
Python kennt nur einen Typ von Modulobjekt, und alle Module gehören zu diesem Typ, unabhängig davon, ob das Modul in Python, C oder einer anderen Sprache implementiert ist. Um die Organisation von Modulen zu erleichtern und eine Namenshierarchie bereitzustellen, gibt es in Python das Konzept der Pakete.
Man kann sich Pakete wie Verzeichnisse in einem Dateisystem und Module wie Dateien innerhalb dieser Verzeichnisse vorstellen, sollte diese Analogie jedoch nicht allzu wörtlich nehmen, da Pakete und Module nicht unbedingt aus dem Dateisystem stammen müssen. Für die Zwecke dieser Dokumentation werden wir diese praktische Analogie von Verzeichnissen und Dateien verwenden. Wie Verzeichnisse im Dateisystem sind Pakete hierarchisch organisiert, und Pakete können ihrerseits sowohl Unterpakete als auch reguläre Module enthalten.
Man sollte unbedingt beachten, dass alle Pakete Module sind, aber nicht alle Module Pakete sind. Oder anders ausgedrückt: Pakete sind lediglich eine besondere Art von Modulen. Genauer gesagt gilt jedes Modul, das ein Attribut __path__ enthält, als Paket.
Alle Module haben einen Namen. Die Namen von Unterpaketen werden durch einen Punkt vom Namen ihres übergeordneten Pakets getrennt, ähnlich wie bei der Standard-Syntax für den Zugriff auf Attribute in Python. So könnte es beispielsweise ein Paket namens email geben, das wiederum ein Unterpaket namens email.mime enthält, in dem sich ein Modul namens email.mime.text befindet.
5.2.1. Reguläre Pakete¶
Python definiert zwei Arten von Paketen: reguläre Pakete und Namespace-Pakete. Reguläre Pakete sind herkömmliche Pakete, wie sie bereits in Python 3.2 und früheren Versionen existierten. Ein reguläres Paket wird in der Regel als Verzeichnis implementiert, das eine Datei __init__.py enthält. Wenn ein reguläres Paket importiert wird, wird diese Datei __init__.py implizit ausgeführt, und die darin definierten Objekte werden an Namen im Namensraum des Pakets gebunden. Die Datei __init__.py kann denselben Python-Code enthalten wie jedes andere Modul auch, und Python fügt dem Modul beim Importieren einige zusätzliche Attribute hinzu.
Beispielsweise definiert die folgende Dateisystemstruktur ein Paket der obersten Ebene parent mit drei Unterpaketen:
parent/
__init__.py
one/
__init__.py
two/
__init__.py
three/
__init__.py
Durch das Importieren von parent.one werden implizit die Dateien parent/__init__.py und parent/one/__init__.py ausgeführt. Bei nachfolgenden Importen von parent.two oder parent.three werden jeweils die Dateien parent/two/__init__.py und parent/three/__init__.py ausgeführt.
Ein Unterverzeichnis innerhalb eines regulären Pakets, das keine Datei __init__.py enthält, wird als implizites namespace-Paket (ein „Namespace-Unterpaket“) behandelt, dessen Stammverzeichnis das übergeordnete Paket ist. Die zugrunde liegende Spezifikation finden Sie unter PEP 420.
5.2.2. Namespace-Pakete¶
Ein Namespace-Paket ist eine Zusammensetzung aus verschiedenen Teilen, wobei jeder Teil ein Unterpaket zum übergeordneten Paket beiträgt. Teile können sich an verschiedenen Orten im Dateisystem befinden. Teile können sich auch in ZIP-Dateien, im Netzwerk oder an jedem anderen Ort befinden, den Python beim Import durchsucht. Namespace-Pakete müssen nicht unbedingt direkt mit Objekten im Dateisystem korrespondieren; es kann sich um virtuelle Module handeln, die keine konkrete Darstellung haben.
Namespace-Pakete verwenden für ihr Attribut __path__ keine gewöhnliche Liste. Stattdessen nutzen sie einen benutzerdefinierten iterierbaren Typ, der beim nächsten Importversuch innerhalb dieses Pakets automatisch eine neue Suche nach Paketteilen durchführt, falls sich der Pfad ihres übergeordneten Pakets (oder sys.path bei einem Paket der obersten Ebene) ändert.
Bei Namespace-Paketen gibt es keine Datei parent/__init__.py . Tatsächlich können bei der Importsuche mehrere Verzeichnisse parent gefunden werden, wobei jedes von einem anderen Teil bereitgestellt wird. Daher befindet sich parent/one möglicherweise nicht physisch neben „ parent/two . In diesem Fall erstellt Python ein Namespace-Paket für das Paket parent auf oberster Ebene, sobald dieses oder eines seiner Unterpakete importiert wird.
Namespace-Pakete können auch innerhalb eines regulären Pakets verschachtelt sein. Wenn das Import-System die Datei __path__ eines regulären Pakets durchsucht und auf ein Unterverzeichnis stößt, das keine __init__.py -Datei enthält, wird dieses Unterverzeichnis zu einem Teil, der zu einem Namespace-Unterpaket des umgebenden regulären Pakets beiträgt.
Siehe auch PEP 420 für die Spezifikation des Namespace-Pakets.
5.3. Suchen¶
Um die Suche zu starten, benötigt Python den vollständig qualifizierten Namen des Moduls (oder Pakets; für die Zwecke dieser Erörterung ist der Unterschied jedoch unerheblich), das importiert werden soll. Dieser Name kann aus verschiedenen Argumenten der Anweisung import oder aus den Parametern der Funktionen importlib.import_module() oder __import__() stammen.
Dieser Name wird in verschiedenen Phasen der Import-Suche verwendet und kann den durch Punkte getrennten Pfad zu einem Submodul darstellen, z. B. foo.bar.baz. In diesem Fall versucht Python zunächst, foo zu importieren, dann foo.bar und schließlich foo.bar.baz. Wenn einer der Zwischenimporte fehlschlägt, wird ein ModuleNotFoundError ausgelöst.
5.3.1. Der Modul-Cache¶
Bei der Suche nach importierten Modulen wird zunächst unter sys.modules nachgeschaut. Diese Zuordnung dient als Cache für alle Module, die zuvor importiert wurden, einschließlich der Zwischenpfade. Wenn also foo.bar.baz zuvor importiert wurde, enthält sys.modules Einträge für foo, foo.bar und foo.bar.baz. Jeder Schlüssel hat als Wert das entsprechende Modulobjekt.
Beim Import wird der Modulname unter sys.modules nachgeschlagen. Ist dort ein Eintrag vorhanden, entspricht der zugehörige Wert dem Modul, das den Import erfüllt, und der Vorgang wird abgeschlossen. Ist der Wert jedoch None, wird eine Ausnahme vom Typ ModuleNotFoundError ausgelöst. Fehlt der Modulname, setzt Python die Suche nach dem Modul fort.
sys.modules ist beschreibbar. Das Löschen eines Schlüssels führt möglicherweise nicht zur Löschung des zugehörigen Moduls (da andere Module möglicherweise Verweise darauf enthalten), macht jedoch den Cache-Eintrag für das genannte Modul ungültig, sodass Python bei dessen nächstem Import erneut nach dem genannten Modul sucht. Dem Schlüssel kann auch der Wert None zugewiesen werden, wodurch der nächste Import des Moduls zwangsläufig zu einem ModuleNotFoundError führt.
Beachten Sie jedoch: Wenn Sie einen Verweis auf das Modulobjekt beibehalten, dessen Cache-Eintrag unter sys.modules ungültig machen und anschließend das genannte Modul erneut importieren, sind die beiden Modulobjekte nicht identisch. Im Gegensatz dazu wird bei importlib.reload() das gleiche Modulobjekt wiederverwendet und der Inhalt des Moduls durch erneutes Ausführen des Modulcodes einfach neu initialisiert.
5.3.2. Finders und Loader¶
Wird das angegebene Modul nicht unter sys.modules gefunden, wird das Importprotokoll von Python aufgerufen, um das Modul zu suchen und zu laden. Dieses Protokoll besteht aus zwei konzeptionellen Objekten: finders und loaders. Die Aufgabe eines Finders besteht darin, mithilfe aller ihm bekannten Strategien festzustellen, ob er das angegebene Modul finden kann. Objekte, die beide dieser Schnittstellen implementieren, werden als Importer bezeichnet – sie geben sich selbst zurück, wenn sie feststellen, dass sie das angeforderte Modul laden können.
Python enthält eine Reihe von Standard-Findern und -Importern. Der erste findet eingebaute Module, der zweite findet „frozen“ Module. Ein dritter Standard-Finder durchsucht einen Importpfad nach Modulen. Der Importpfad ist eine Liste von Speicherorten, die Dateisystempfade oder ZIP-Dateien bezeichnen können. Er lässt sich auch erweitern, um nach beliebigen auffindbaren Ressourcen zu suchen, beispielsweise solchen, die durch URLs identifiziert werden.
Die Importmechanismen sind erweiterbar, sodass neue Suchfunktionen hinzugefügt werden können, um den Umfang und die Reichweite der Modulsuche zu erweitern.
Finder laden Module nicht tatsächlich. Wenn sie das angegebene Modul finden können, geben sie eine Modulspezifikation zurück, eine Kapselung der importbezogenen Informationen des Moduls, die der Importmechanismus dann beim Laden des Moduls verwendet.
In den folgenden Abschnitten wird das Protokoll für Finder und Loader näher beschrieben, einschließlich der Vorgehensweise, wie Sie neue Finder und Loader erstellen und registrieren können, um die Importmechanismen zu erweitern.
Geändert in Version 3.4: In früheren Python-Versionen gaben Suchfunktionen direkt Loader zurück, während sie nun Modulspezifikationen zurückgeben, die Loader enthalten. Loader werden beim Import weiterhin verwendet, haben jedoch weniger Aufgaben.
5.3.3. Import-Hooks¶
Die Import-Mechanismen sind so konzipiert, dass sie erweiterbar sind; der wichtigste Mechanismus hierfür sind die Import-Hooks. Es gibt zwei Arten von Import-Hooks: Meta-Hooks und Import-Pfad-Hooks.
Meta-Hooks werden zu Beginn der Importverarbeitung aufgerufen, noch bevor andere Importvorgänge stattfinden – mit Ausnahme der Cache-Abfrage unter sys.modules “. Dadurch können Meta-Hooks die Verarbeitung von sys.path, eingefrorene Module oder sogar integrierte Module überschreiben. Meta-Hooks werden registriert, indem neue Finder-Objekte zu „ sys.meta_path hinzugefügt werden, wie im Folgenden beschrieben.
Import-Pfad-Hooks werden im Rahmen der Verarbeitung von sys.path (oder package.__path__ ) an der Stelle aufgerufen, an der das zugehörige Pfadelement gefunden wird. Import-Pfad-Hooks werden registriert, indem neue aufrufbare Funktionen wie unten beschrieben zu sys.path_hooks hinzugefügt werden.
5.3.4. Der Metapfad¶
Wenn das genannte Modul unter sys.modules nicht gefunden wird, durchsucht Python als Nächstes sys.meta_path, das eine Liste von Meta-Path-Finder-Objekten enthält. Diese Finder werden abgefragt, um festzustellen, ob sie wissen, wie das genannte Modul zu behandeln ist. Meta-Pfadfinder müssen eine Methode namens find_spec() implementieren, die drei Argumente entgegennimmt: einen Namen, einen Importpfad und (optional) ein Zielmodul. Der Meta-Pfadfinder kann eine beliebige Strategie anwenden, um festzustellen, ob er das genannte Modul verarbeiten kann oder nicht.
Wenn der Meta-Path-Finder weiß, wie das benannte Modul zu verarbeiten ist, gibt er ein Spec-Objekt zurück. Kann er das benannte Modul nicht verarbeiten, gibt er None zurück. Erreicht die Verarbeitung von sys.meta_path das Ende der Liste, ohne ein Spec zurückzugeben, wird eine ModuleNotFoundError -Ausnahme ausgelöst. Alle anderen ausgelösten Ausnahmen werden einfach nach oben weitergeleitet, wodurch der Importvorgang abgebrochen wird.
Die Methode find_spec() von Meta-Pfadfindern wird mit zwei oder drei Argumenten aufgerufen. Das erste Argument ist der vollqualifizierte Name des zu importierenden Moduls, zum Beispiel foo.bar.baz. Das zweite Argument enthält die Pfadeinträge, die für die Modulsuche verwendet werden sollen. Bei Modulen der obersten Ebene ist das zweite Argument None, bei Submodulen oder Subpaketen hingegen ist das zweite Argument der Wert des Attributs __path__ des übergeordneten Pakets. Kann nicht auf das entsprechende Attribut __path__ zugegriffen werden, wird eine ModuleNotFoundError ausgelöst. Das dritte Argument ist ein vorhandenes Modulobjekt, das später als Ziel für das Laden dient. Das Import-System übergibt ein Zielmodul nur während des Neuladens.
Der Metapfad kann bei einer einzelnen Importanforderung mehrfach durchlaufen werden. Angenommen, keines der beteiligten Module wurde bereits zwischengespeichert, dann führt der Import von foo.bar.baz zunächst einen Import auf oberster Ebene durch, wobei bei jedem Metapfad-Finder (mpf) die Funktion mpf.find_spec("foo", None, None) aufgerufen wird. Nachdem foo importiert wurde, wird foo.bar durch ein zweites Durchlaufen des Metapfads importiert, wobei mpf.find_spec("foo.bar", foo.__path__, None) aufgerufen wird. Sobald foo.bar importiert wurde, ruft der letzte Durchlauf mpf.find_spec("foo.bar.baz", foo.bar.__path__, None) auf.
Einige Meta-Path-Finder unterstützen nur Importe auf oberster Ebene. Diese Importer geben stets None zurück, wenn als zweites Argument etwas anderes als None übergeben wird.
Pythons Standard- sys.meta_path verfügt über drei Meta-Pfadfinder: einen, der weiß, wie man integrierte Module importiert, einen, der weiß, wie man „frozen“ Module importiert, und einen, der weiß, wie man Module aus einem Importpfad importiert (d. h. den pfadbasierten Finder).
Geändert in Version 3.4: Die Methode find_spec() der Meta-Path-Finder hat find_module() ersetzt, die nun veraltet ist. Sie funktioniert zwar weiterhin unverändert, doch der Importmechanismus greift nur dann darauf zurück, wenn der Finder find_spec() nicht implementiert.
Geändert in Version 3.10: Die Verwendung von find_module() durch das Import-System löst nun den Fehler ImportWarning aus.
Geändert in Version 3.12: find_module() wurde entfernt. Verwenden Sie stattdessen find_spec().
5.4. Module laden¶
Wird eine Modulspezifikation gefunden, verwendet der Importmechanismus diese (und den darin enthaltenen Loader) beim Laden des Moduls. Hier ist eine grobe Beschreibung dessen, was während des Ladevorgangs von „import:“ geschieht:
module = None
if spec.loader is not None and hasattr(spec.loader, 'create_module'):
# Es wird davon ausgegangen, dass "exec_module" ebenfalls auf dem Loader definiert ist..
module = spec.loader.create_module(spec)
if module is None:
module = ModuleType(spec.name)
# Die importbezogenen Modulattribute werden hier gesetzt.:
_init_module_attrs(spec, module)
if spec.loader is None:
# nicht unterstützt
raise ImportError
if spec.origin is None and spec.submodule_search_locations is not None:
# Namensraumpaket
sys.modules[spec.name] = module
elif not hasattr(spec.loader, 'exec_module'):
module = spec.loader.load_module(spec.name)
else:
sys.modules[spec.name] = module
try:
spec.loader.exec_module(module)
except BaseException:
try:
del sys.modules[spec.name]
except KeyError:
pass
raise
return sys.modules[spec.name]
Beachte bitte folgende Details:
Wenn unter
sys.modulesbereits ein Modulobjekt mit dem angegebenen Namen vorhanden ist, hat „import“ dieses bereits zurückgegeben.Das Modul wird in
sys.modulesvorhanden sein, bevor der Loader den Modulcode ausführt. Dies ist von entscheidender Bedeutung, da der Modulcode sich möglicherweise (direkt oder indirekt) selbst importiert; durch das vorherige Hinzufügen zusys.moduleswird im schlimmsten Fall eine unbegrenzte Rekursion und im besten Fall ein mehrfaches Laden verhindert.Wenn das Laden fehlschlägt, wird das fehlerhafte Modul – und nur das fehlerhafte Modul – aus
sys.modulesentfernt. Alle Module, die sich bereits im Cache vonsys.modulesbefinden, sowie alle Module, die als Nebeneffekt erfolgreich geladen wurden, müssen im Cache verbleiben. Dies steht im Gegensatz zum Neuladen, bei dem selbst das fehlerhafte Modul insys.modulesverbleibt.Nachdem das Modul erstellt wurde, aber noch vor seiner Ausführung, legt der Importmechanismus die importbezogenen Modulattribute fest („_init_module_attrs“ im obigen Pseudocode-Beispiel), wie in einem späteren Abschnitt zusammengefasst.
Die Modulausführung ist der entscheidende Moment beim Laden, in dem der Namensraum des Moduls gefüllt wird. Die Ausführung wird vollständig an den Loader delegiert, der entscheidet, was wie gefüllt wird.
Das beim Laden erstellte und an exec_module() übergebene Modul ist möglicherweise nicht dasselbe, das am Ende des Importvorgangs von [2] zurückgegeben wird.
Geändert in Version 3.4: Das Import-System hat die Routineaufgaben der Loader übernommen. Diese wurden zuvor von der Methode importlib.abc.Loader.load_module() ausgeführt.
5.4.1. Modullader¶
Modullader erfüllen die entscheidende Funktion des Ladens: die Ausführung des Moduls. Der Importmechanismus ruft die Methode importlib.abc.Loader.exec_module() mit einem einzigen Argument auf, nämlich dem auszuführenden Modulobjekt. Jeder von exec_module() zurückgegebene Wert wird ignoriert.
Lader müssen die folgenden Anforderungen erfüllen:
Handelt es sich bei dem Modul um ein Python-Modul (im Gegensatz zu einem integrierten Modul oder einer dynamisch geladenen Erweiterung), sollte der Loader den Code des Moduls im globalen Namensraum des Moduls ausführen (
module.__dict__).Wenn der Loader das Modul nicht ausführen kann, sollte er eine
ImportErrorauslösen; alle anderen Ausnahmen, die währendexec_module()ausgelöst werden, werden jedoch weitergegeben.
In vielen Fällen können Finder und Loader dasselbe Objekt sein; in solchen Fällen würde die Methode find_spec() lediglich eine Spezifikation zurückgeben, bei der der Loader auf self gesetzt ist.
Modullader können sich dafür entscheiden, das Modulobjekt während des Ladens zu erstellen, indem sie eine Methode create_module() implementieren. Diese Methode nimmt ein Argument entgegen – die Modulspezifikation – und gibt das neue Modulobjekt zurück, das während des Ladens verwendet werden soll. create_module() muss keine Attribute für das Modulobjekt festlegen. Wenn die Methode None zurückgibt, erstellt der Importmechanismus das neue Modul selbst.
Added in version 3.4: Die create_module() -Methode bei Modullader.
Geändert in Version 3.4: Die Methode load_module() wurde durch exec_module() ersetzt, und die Import-Mechanismen übernahmen alle Routineaufgaben beim Laden.
Um die Kompatibilität mit bestehenden Ladern zu gewährleisten, verwendet die Import-Maschinerie die Methode load_module() der Lader, sofern diese vorhanden ist und der Lader nicht zusätzlich exec_module() implementiert. load_module() ist jedoch veraltet, und Lader sollten stattdessen exec_module() implementieren.
Die Methode load_module() muss neben der Ausführung des Moduls auch alle oben beschriebenen Standardfunktionen zum Laden des Moduls implementieren. Es gelten dieselben Einschränkungen, wobei einige zusätzliche Erläuterungen zu beachten sind:
Wenn in
sys.modulesbereits ein Modulobjekt mit dem angegebenen Namen vorhanden ist, muss der Loader dieses vorhandene Modul verwenden. (Andernfalls funktioniertimportlib.reload()nicht ordnungsgemäß.) Wenn das genannte Modul insys.modulesnicht vorhanden ist, muss der Loader ein neues Modulobjekt erstellen und es zusys.moduleshinzufügen.Das Modul muss in
sys.modulesvorhanden sein, bevor der Loader den Modulcode ausführt, um unbegrenzte Rekursion oder mehrfaches Laden zu verhindern.Sollte das Laden fehlschlagen, muss der Loader alle Module entfernen, die er in
sys.moduleseingefügt hat; dabei darf er jedoch nur die fehlerhaften Module entfernen, und zwar nur dann, wenn der Loader diese Module selbst explizit geladen hat.
Geändert in Version 3.5: Eine DeprecationWarning wird ausgelöst, wenn exec_module() “ definiert ist, create_module() jedoch nicht.
Geändert in Version 3.6: Eine ImportError -Ausnahme wird ausgelöst, wenn exec_module() definiert ist, create_module() jedoch nicht.
Geändert in Version 3.10: Die Verwendung von load_module() führt zu ImportWarning .
5.4.2. Untermodule¶
Wenn ein Submodul über einen beliebigen Mechanismus geladen wird (z. B. über die importlib -APIs, die Anweisungen import oder import-from oder die integrierte Funktion __import__() ), wird im Namensraum des übergeordneten Moduls eine Bindung zum Submodul-Objekt hergestellt. Wenn beispielsweise das Paket spam ein Submodul foo enthält, verfügt spam nach dem Import von spam.foo über ein Attribut foo, das an das Submodul gebunden ist. Nehmen wir an, Sie haben die folgende Verzeichnisstruktur:
spam/
__init__.py
foo.py
und die Datei spam/__init__.py enthält die folgende Zeile:
from .foo import Foo
Wenn Sie anschließend den folgenden Befehl ausführen, werden im Modul spam Namenszuordnungen für foo und Foo festgelegt:
>>> import spam
>>> spam.foo
<module 'spam.foo' from '/tmp/imports/spam/foo.py'>
>>> spam.Foo
<class 'spam.foo.Foo'>
Angesichts der bekannten Regeln zur Namensbindung in Python mag dies überraschend erscheinen, ist jedoch tatsächlich ein grundlegendes Merkmal des Import-Systems. Die unveränderliche Regel lautet: Wenn Sie sys.modules['spam'] und sys.modules['spam.foo'] haben (wie es nach dem obigen Import der Fall wäre), muss Letzteres als Attribut foo von Ersterem erscheinen.
5.4.3. Modulspezifikationen¶
Die Import-Maschinerie nutzt beim Import, insbesondere vor dem Laden, eine Vielzahl von Informationen zu jedem Modul. Die meisten dieser Informationen gelten für alle Module gleichermaßen. Der Zweck der Spezifikation eines Moduls besteht darin, diese importbezogenen Informationen modulweise zu kapseln.
Die Verwendung einer Spezifikation beim Import ermöglicht die Übertragung des Zustands zwischen den Komponenten des Importsystems, z. B. zwischen dem Finder, der die Modulspezifikation erstellt, und dem Loader, der sie ausführt. Vor allem ermöglicht sie es dem Importmechanismus, die Routinevorgänge des Ladens durchzuführen, während ohne eine Modulspezifikation diese Aufgabe beim Loader lag.
Die Spezifikation des Moduls ist unter module.__spec__ verfügbar. Die entsprechende Einstellung von __spec__ gilt gleichermaßen für Module, die beim Start des Interpreters initialisiert werden. Die einzige Ausnahme bildet __main__, wo __spec__ in einigen Fällen auf None gesetzt wird.
Weitere Informationen zum Inhalt der Modulspezifikation finden Sie unter ModuleSpec.
Added in version 3.4.
5.4.4. __path__-Attribute in Modulen¶
Das Attribut __path__ sollte eine (möglicherweise leere) Sequenz von Zeichenketten sein, die die Speicherorte auflistet, an denen die Submodule des Pakets zu finden sind. Per Definition gilt: Wenn ein Modul über ein Attribut __path__ verfügt, handelt es sich um ein Paket.
Das Attribut __path__ eines Pakets wird beim Import seiner Unterpakete verwendet. Innerhalb des Importmechanismus funktioniert es ähnlich wie sys.path, d. h., es liefert eine Liste von Speicherorten, an denen beim Import nach Modulen gesucht wird. Allerdings ist __path__ in der Regel wesentlich stärker eingeschränkt als sys.path .
Die gleichen Regeln, die für sys.path gelten, gelten auch für die __path__ eines Pakets. sys.path_hooks (siehe unten) werden beim Durchlaufen der __path__ eines Pakets herangezogen.
Die Datei __init__.py eines Pakets kann das Attribut __path__ des Pakets festlegen oder ändern; auf diese Weise wurden Namespace-Pakete vor der Einführung von PEP 420 in der Regel implementiert. Mit der Einführung von PEP 420 müssen Namespace-Pakete keine __init__.py “-Dateien mehr bereitstellen, die ausschließlich Code zur Bearbeitung von __path__ enthalten; die Import-Mechanik legt __path__ für das Namespace-Paket automatisch korrekt fest.
5.4.5. Modul-Repräsentanten¶
Standardmäßig verfügen alle Module über eine verwendbare „repr“ (Repräsentanten) Darstellung. Je nach den oben festgelegten Attributen und den Angaben in der Modul-Spezifikation können Sie die „repr“-Darstellung von Modulobjekten jedoch expliziter steuern.
Wenn das Modul über eine Spezifikation (__spec__) verfügt, versucht der Importmechanismus, daraus eine Repr zu generieren. Sollte dies fehlschlagen oder keine Spezifikation vorhanden sein, erstellt das Importsystem eine Standard-Repr unter Verwendung der für das Modul verfügbaren Informationen. Dabei versucht es, die Dateien module.__name__, module.__file__ und module.__loader__ als Eingabe für die Repr zu verwenden, wobei fehlende Informationen durch Standardwerte ersetzt werden.
Hier sind die genauen Regeln, die angewendet wurden:
Verfügt das Modul über ein Attribut
__spec__, werden die Informationen in der Spezifikation zur Generierung der Repr verwendet. Dabei werden die Attribute „name“, „loader“, „origin“ und „has_location“ herangezogen.Wenn das Modul über ein Attribut
__file__verfügt, wird dieses als Teil der „repr“-Darstellung des Moduls verwendet.Wenn das Modul kein
__file__hat, aber über ein__loader__verfügt, das nichtNoneist, wird die repr des Laders als Teil der repr des Moduls verwendet.Ansonsten verwende einfach die Methode
__name__des Moduls in der „repr“-Anweisung.
Geändert in Version 3.12: Die Verwendung von module_repr(), die bereits seit Python 3.4 als veraltet gilt, wurde in Python 3.12 entfernt und wird bei der Auflösung des repr-Objekts eines Moduls nicht mehr aufgerufen.
5.4.6. Ungültigmachung von zwischengespeichertem Bytecode¶
Bevor Python zwischengespeicherten Bytecode aus einer .pyc -Datei lädt, prüft es, ob der Cache mit der Quelldatei .py auf dem neuesten Stand ist. Standardmäßig speichert Python dazu beim Schreiben der Cache-Datei den Zeitstempel der letzten Änderung sowie die Größe der Quelldatei in der Cache-Datei. Zur Laufzeit überprüft das Import-System dann die Cache-Datei, indem es die darin gespeicherten Metadaten mit den Metadaten der Quell vergleicht.
Python unterstützt außerdem „hashbasierte“ Cache-Dateien, die anstelle der Metadaten einen Hash des Inhalts der Quelldatei speichern. Es gibt zwei Varianten hashbasierter .pyc -Dateien: geprüfte und ungeprüfte. Bei geprüften hashbasierten .pyc -Dateien überprüft Python die Cache-Datei, indem es die Quelldatei hasht und den resultierenden Hash mit dem Hash in der Cache-Datei vergleicht. Wird eine geprüfte hash-basierte Cache-Datei als ungültig erkannt, generiert Python sie neu und schreibt eine neue geprüfte hash-basierte Cache-Datei. Bei ungeprüften hash-basierten .pyc -Dateien geht Python einfach davon aus, dass die Cache-Datei gültig ist, sofern sie vorhanden ist. Das Validierungsverhalten von hash-basierten .pyc -Dateien kann mit dem Flag --check-hash-based-pycs überschrieben werden.
Geändert in Version 3.7: Es wurden Hash-basierte .pyc -Dateien hinzugefügt. Bisher unterstützte Python nur die zeitstempelbasierte Ungültigkeitserklärung von Bytecode-Caches.
5.5. Der pfadbasierte Finder¶
Wie bereits erwähnt, verfügt Python über mehrere standardmäßige Metapfad-Suchfunktionen. Eine davon, der sogenannte pfadbasierte Sucher (PathFinder), durchsucht einen Importpfad, der eine Liste von Pfadeinträgen enthält. Jeder Pfadeintrag gibt einen Speicherort an, an dem nach Modulen gesucht werden soll.
Der pfadbasierte Finder selbst weiß nicht, wie man etwas importiert. Stattdessen durchläuft er die einzelnen Pfadeinträge und ordnet jedem von ihnen einen Pfadeintrags-Finder zu, der weiß, wie man mit dieser bestimmten Art von Pfad umgeht.
Die standardmäßigen Pfadeintrags-Finder implementieren die gesamte Semantik zum Auffinden von Modulen im Dateisystem und unterstützen dabei spezielle Dateitypen wie Python-Quellcode (.py -Dateien), Python-Bytecode (.pyc -Dateien) und gemeinsam genutzte Bibliotheken (z. B. .so -Dateien). Sofern dies vom Modul zipimport in der Standardbibliothek unterstützt wird, übernehmen die standardmäßigen Pfadeintrags-Finder auch das Laden all dieser Dateitypen (mit Ausnahme von gemeinsam genutzten Bibliotheken) aus ZIP-Dateien.
Pfadeinträge müssen sich nicht auf Speicherorte im Dateisystem beschränken. Sie können sich auf URLs, Datenbankabfragen oder jeden anderen Speicherort beziehen, der als Zeichenfolge angegeben werden kann.
Der pfadbasierte Finder bietet zusätzliche Hooks und Protokolle, mit denen du die Arten der durchsuchbaren Pfadeinträge erweitern und anpassen können. Wenn du beispielsweise Pfadeinträge als Netzwerk-URLs unterstützen möchtest, kannst du einen Hook schreiben, der HTTP-Semantik implementiert, um Module im Internet zu finden. Dieser Hook (ein Callable) würde einen Pfadeintrags-Finder zurückgeben, der das unten beschriebene Protokoll unterstützt, mit dem dann ein Loader für das Modul aus dem Web abgerufen würde.
Eine Anmerkung: Sowohl in diesem Abschnitt als auch im vorherigen wird der Begriff Finder verwendet, wobei durch die Begriffe Meta-Pfad-Finder und Pfad-Eintrag-Finder zwischen ihnen unterschieden wird. Diese beiden Arten von Findern sind sich sehr ähnlich, unterstützen ähnliche Protokolle und funktionieren während des Importvorgangs auf ähnliche Weise, doch es ist wichtig zu beachten, dass sie sich in subtilen Punkten unterscheiden. Insbesondere werden Meta-Pfad-Finder zu Beginn des Importvorgangs ausgeführt, ausgehend von der Durchquerung des Verzeichnisses sys.meta_path .
Im Gegensatz dazu sind Pfad-Eintrag-Finder gewissermaßen ein Implementierungsdetail des pfadbasierten Finders, und tatsächlich würde, wenn der pfadbasierte Finder aus sys.meta_path entfernt würde, keine der Semantiken der Pfadeintrags-Finder aufgerufen werden.
5.5.1. Pfadeintrag-Suchfunktionen¶
Der pfadbasierte Finder ist dafür zuständig, Python-Module und -Pakete zu finden und zu laden, deren Speicherort durch einen String als Pfadeintrag angegeben wird. Die meisten Pfadeinträge bezeichnen Speicherorte im Dateisystem, müssen sich jedoch nicht darauf beschränken.
Als Meta-Pfadfinder implementiert der pfadbasierte Finder das zuvor beschriebene Protokoll find_spec(), stellt jedoch zusätzliche Hooks bereit, mit denen sich die Art und Weise anpassen lässt, wie Module über den Importpfad gefunden und geladen werden.
Der path based finder verwendet drei Variablen: sys.path, sys.path_hooks und sys.path_importer_cache. Zudem werden die Attribute __path__ der Paketobjekte herangezogen. Diese bieten zusätzliche Möglichkeiten zur Anpassung des Importmechanismus.
sys.path enthält eine Liste von Zeichenketten, die Suchpfade für Module und Pakete angeben. Sie wird anhand der Umgebungsvariablen PYTHONPATH sowie verschiedener anderer installations- und implementierungsspezifischer Standardwerte initialisiert. Einträge in sys.path können Verzeichnisse im Dateisystem, ZIP-Dateien und möglicherweise andere „Speicherorte“ (siehe das Modul site) benennen, an denen nach Modulen gesucht werden soll, wie z. B. URLs oder Datenbankabfragen. In sys.path dürfen nur Zeichenketten enthalten sein; alle anderen Datentypen werden ignoriert.
Der pfadbasierte Finder ist ein Meta-Pfadfinder, daher beginnt der Importmechanismus die Suche nach dem Importpfad, indem er – wie zuvor beschrieben – die Methode find_spec() des pfadbasierten Finders aufruft. Wenn das Argument path an find_spec() übergeben wird, handelt es sich um eine Liste von String-Pfaden, die durchlaufen werden sollen – typischerweise das Attribut __path__ eines Pakets für einen Import innerhalb dieses Pakets. Wenn das Argument path den Wert None hat, deutet dies auf einen Import auf oberster Ebene hin, und es wird sys.path verwendet.
Der pfadbasierte Finder durchläuft jeden Eintrag im Suchpfad und sucht für jeden dieser Einträge nach einem geeigneten Pfadeintrags-Finder (PathEntryFinder) für den Pfadeintrag. Da dies ein ressourcenintensiver Vorgang sein kann (z. B. können bei dieser Suche Overheads durch stat() “-Aufrufe entstehen), verwaltet der pfadbasierte Finder einen Cache, der Pfadeinträge den Pfadeintrags-Findern zuordnet. Dieser Cache wird in sys.path_importer_cache verwaltet (trotz des Namens speichert dieser Cache tatsächlich Finder-Objekte und ist nicht auf Importer-Objekte beschränkt). Auf diese Weise muss die aufwendige Suche nach dem Pfadeintrags-Finder für einen bestimmten Pfadeintrag nur einmal durchgeführt werden. Benutzercode kann Cache-Einträge aus sys.path_importer_cache entfernen und so den pfadbasierten Finder dazu zwingen, die Suche nach dem Pfadeintrag erneut durchzuführen.
Ist der Pfadeintrag nicht im Cache vorhanden, durchläuft der pfadbasierte Finder alle aufrufbaren Funktionen in sys.path_hooks. Jeder der Pfadeintrag-Hooks in dieser Liste wird mit einem einzigen Argument aufgerufen, nämlich dem zu durchsuchenden Pfadeintrag. Diese aufrufbare Funktion kann entweder einen Pfadeintrags-Finder zurückgeben, der den Pfadeintrag verarbeiten kann, oder sie kann eine ImportError auslösen. Ein ImportError wird vom pfadbasierten Finder verwendet, um zu signalisieren, dass der Hook keinen Pfadeintrags-Finder für diesen Pfadeintrag finden kann. Die Ausnahme wird ignoriert und die Iteration über den Importpfad wird fortgesetzt. Der Hook sollte entweder eine Zeichenkette oder ein Byte-Objekt erwarten; die Kodierung von Byte-Objekten bleibt dem Hook überlassen (z. B. kann es sich um eine Dateisystemkodierung, UTF-8 oder etwas anderes handeln), und wenn der Hook das Argument nicht dekodieren kann, sollte er eine ImportError -Ausnahme auslösen.
Wenn die Iteration von sys.path_hooks ohne Rückgabe eines Pfadeintrags-Finders endet, speichert die Methode find_spec() des pfadbasierten Finders None in sys.path_importer_cache (um anzuzeigen, dass es keinen Finder für diesen Pfadeintrag gibt) und gibt None zurück, was darauf hinweist, dass dieser Meta-Pfad-Finder das Modul nicht finden konnte.
Wenn von einem der auf sys.path_hooks aufgeführten path entry hook-Aufrufobjekte ein path entry finder zurückgegeben wird, wird das folgende Protokoll verwendet, um vom Finder eine Modulspezifikation abzufragen, die dann beim Laden des Moduls verwendet wird.
The current working directory – denoted by an empty string – is handled
slightly differently from other entries on sys.path. First, if the
current working directory is found to not exist, no value is stored in
sys.path_importer_cache. Second, the value for the current working
directory is looked up fresh for each module lookup. Third, the path used for
sys.path_importer_cache and returned by
importlib.machinery.PathFinder.find_spec() will be the actual current
working directory and not the empty string.
5.5.2. Protokoll zur Ermittlung von Pfadeinträgen¶
Um das Importieren von Modulen und initialisierten Paketen zu unterstützen und zudem Teile zu Namespace-Paketen beizusteuern, müssen Pfadefinder die Methode find_spec() implementieren.
find_spec() nimmt zwei Argumente entgegen: den vollqualifizierten Namen des zu importierenden Moduls und das (optionale) Zielmodul. find_spec() gibt eine vollständig ausgefüllte Spezifikation für das Modul zurück. In dieser Spezifikation ist „loader“ immer gesetzt (mit einer Ausnahme).
Um der Import-Maschinerie mitzuteilen, dass die Spezifikation einen Namensraum portion darstellt, setzt der Pfadeintrags-Finder submodule_search_locations auf eine Liste, die diesen Teil enthält.
Geändert in Version 3.4: find_spec() ersetzt find_loader() und find_module(), die beide mittlerweile veraltet sind, jedoch verwendet werden, wenn find_spec() nicht definiert ist.
Ältere Pfadeintrags-Finder verwenden möglicherweise eine dieser beiden veralteten Methoden anstelle von find_spec(). Aus Gründen der Abwärtskompatibilität werden diese Methoden weiterhin berücksichtigt. Wenn jedoch find_spec() im Pfadeintrags-Finder implementiert ist, werden die veralteten Methoden ignoriert.
find_loader() nimmt ein Argument entgegen, nämlich den vollqualifizierten Namen des zu importierenden Moduls. find_loader() ` gibt ein 2-Tupel zurück, dessen erstes Element der Loader und dessen zweites Element ein Namespace-Teil portion ist.
Aus Gründen der Abwärtskompatibilität mit anderen Implementierungen des Importprotokolls unterstützen viele Pfadeintrags-Finder ebenfalls dieselbe traditionelle Methode find_module(), die auch von Meta-Pfad-Findern unterstützt wird. Die Methoden find_module() der Pfadeintrags-Finder werden jedoch niemals mit einem Argument vom Typ path aufgerufen (es wird erwartet, dass sie die entsprechenden Pfadinformationen aus dem ersten Aufruf des Pfad-Hooks erfassen).
Die Methode find_module() bei Pfadeintragsfindern ist veraltet, da sie es dem Pfadeintragsfinder nicht ermöglicht, Teile zu Namespace-Paketen beizusteuern. Wenn bei einem Pfadeintragsfinder sowohl find_loader() als auch find_module() vorhanden sind, ruft das Import-System immer find_loader() vorrangig vor find_module() auf.
Geändert in Version 3.10: Aufrufe von find_module() und find_loader() durch das Importsystem lösen den Fehler ImportWarning aus.
Geändert in Version 3.12: find_module() und find_loader() wurden entfernt.
5.6. Ersetzen des Standard-Importsystems¶
Die zuverlässigste Methode, das gesamte Import-System zu ersetzen, besteht darin, den Standardinhalt von sys.meta_path zu löschen und ihn vollständig durch einen benutzerdefinierten Meta-Path-Hook zu ersetzen.
Wenn es akzeptabel ist, lediglich das Verhalten von Importanweisungen zu ändern, ohne andere APIs zu beeinträchtigen, die auf das Importsystem zugreifen, könnte es ausreichen, die integrierte Funktion __import__() zu ersetzen.
Um den Import bestimmter Module über einen Hook zu Beginn des Meta-Pfads gezielt zu verhindern (anstatt das Standard-Import-System vollständig zu deaktivieren), reicht es aus, direkt von find_spec() aus eine ModuleNotFoundError -Ausnahme auszulösen, anstatt None zurückzugeben. Letzteres signalisiert, dass die Suche im Meta-Pfad fortgesetzt werden soll, während das Auslösen einer Ausnahme diese sofort beendet.
5.7. Paketbezogene Importe¶
Relative Importe werden durch führende Punkte gekennzeichnet. Ein einzelner führender Punkt kennzeichnet einen relativen Import, der vom aktuellen Paket ausgeht. Zwei oder mehr führende Punkte kennzeichnen einen relativen Import zu den übergeordneten Paketen des aktuellen Pakets, wobei jeder Punkt nach dem ersten für eine Ebene steht. Beispielsweise bei folgender Paketstruktur:
package/
__init__.py
subpackage1/
__init__.py
moduleX.py
moduleY.py
subpackage2/
__init__.py
moduleZ.py
moduleA.py
Sowohl in subpackage1/moduleX.py als auch in subpackage1/__init__.py sind die folgenden relativen Importe gültig:
from .moduleY import spam
from .moduleY import spam as ham
from . import moduleY
from ..subpackage1 import moduleY
from ..subpackage2.moduleZ import eggs
from ..moduleA import foo
Absolute Importe können entweder die Syntax import <> oder from <> import <> verwenden, relative Importe hingegen dürfen nur die zweite Form verwenden; der Grund dafür ist, dass:
import XXX.YYY.ZZZ
XXX.YYY.ZZZ sollte als gültiger Ausdruck verfügbar sein, aber .moduleY ist kein gültiger Ausdruck.
5.8. Besondere Hinweise zu __main__¶
Das Modul __main__ stellt im Zusammenhang mit dem Import-System von Python einen Sonderfall dar. Wie bereits an anderer Stelle erwähnt :ref:` <programs>`, wird das Modul __main__ direkt beim Start des Interpreters initialisiert, ähnlich wie sys und builtins. Im Gegensatz zu diesen beiden Modulen gilt es jedoch nicht streng genommen als integriertes Modul. Dies liegt daran, dass die Art und Weise, wie __main__ initialisiert wird, von den Flags und anderen Optionen abhängt, mit denen der Interpreter aufgerufen wird.
5.8.1. __main__.__spec__¶
Je nachdem, wie __main__ initialisiert wird, wird __main__.__spec__ entsprechend gesetzt oder auf None .
Wenn Python mit der Option -m gestartet wird, wird __spec__ auf die Modul-Spezifikation des entsprechenden Moduls oder Pakets gesetzt. __spec__ wird ebenfalls gefüllt, wenn das Modul __main__ im Rahmen der Ausführung eines Eintrags vom Typ „directory“, „zipfile“ oder eines anderen Eintrags vom Typ sys.path geladen wird.
In den übrigen Fällen __main__.__spec__ ist auf None gesetzt, da der Code, der zum Befüllen der __main__ verwendet wird, nicht direkt einem importierbaren Modul entspricht:
interaktive Eingabeaufforderung
-cOptionAusführung über stdin
direkt aus einer Quell- oder Bytecode-Datei ausgeführt
Beachte, dass __main__.__spec__ im letzten Fall immer None ist, selbst wenn die Datei technisch gesehen stattdessen direkt als Modul importiert werden könnte. Verwenden Sie den Schalter -m, wenn gültige Modul-Metadaten in __main__ gewünscht sind.
Beachte außerdem, dass selbst wenn __main__ einem importierbaren Modul entspricht und __main__.__spec__ entsprechend gesetzt ist, diese dennoch als unterschiedliche Module betrachtet werden. Dies liegt daran, dass Blöcke, die durch if __name__ == "__main__": -Prüfungen geschützt sind, nur dann ausgeführt werden, wenn das Modul zum Befüllen des Namensraums __main__ verwendet wird, nicht jedoch während eines normalen Imports.
5.9. Literaturverzeichnis¶
Die Importmechanismen haben sich seit den Anfängen von Python erheblich weiterentwickelt. Die ursprüngliche Spezifikation für Pakete kann nach wie vor eingesehen werden, auch wenn sich einige Details seit der Erstellung dieses Dokuments geändert haben.
Die ursprüngliche Spezifikation für sys.meta_path war unter PEP 302 zu finden und wurde später unter PEP 420 erweitert.
PEP 420 In Python 3.3 wurden Namespace-Pakete eingeführt. In der Version PEP 420 wurde außerdem das Protokoll find_loader() als Alternative zu find_module() eingeführt.
PEP 366 beschreibt die Einführung des Attributs __package__ für explizite relative Importe in Hauptmodulen.
PEP 328 führte absolute und explizite relative Importe ein und schlug zunächst __name__ als Semantik vor, die PEP 366 schließlich für __package__ festlegen würde.
PEP 338 definiert die Ausführung von Modulen als Skripte.
PEP 451 fügt die Kapselung des Importstatus pro Modul in Spezifikationsobjekten hinzu. Zudem werden die meisten Routineaufgaben der Loader wieder auf die Importmechanismen verlagert. Diese Änderungen ermöglichen die Abkündigung mehrerer APIs im Importsystem sowie die Hinzufügung neuer Methoden zu Findern und Loadern.
Fußnoten