5. Das Importsystem

Python-Code in einem Modul erhält Zugriff auf den Code in einem anderen Modul durch den Prozess des Importierens. Die import-Anweisung ist die gebräuchlichste Methode, um die Import-Mechanismen aufzurufen, aber sie ist nicht die einzige. Funktionen wie importlib.import_module() und die eingebaute Funktion __import__() können ebenfalls verwendet werden, um die Import-Mechanismen aufzurufen.

Die import-Anweisung kombiniert zwei Operationen: Sie sucht nach dem benannten Modul und bindet dann die Ergebnisse dieser Suche an einen Namen im lokalen Gültigkeitsbereich. Die Suchoperation der import-Anweisung ist als Aufruf der Funktion __import__() mit den entsprechenden Argumenten definiert. Der Rückgabewert von __import__() wird verwendet, um die Namensbindungsoperation der import-Anweisung durchzuführen. Die genauen Details dieser Namensbindungsoperation finden Sie in der Beschreibung der import-Anweisung.

Ein direkter Aufruf von __import__() führt nur die Modulsuche und, falls gefunden, die Modulerstellungsoperation durch. Während bestimmte Nebeneffekte auftreten können, wie z. B. das Importieren von Elternpaketen und das Aktualisieren verschiedener Caches (einschließlich sys.modules), führt nur die import-Anweisung eine Namensbindungsoperation durch.

Wenn eine import-Anweisung ausgeführt wird, wird die standardmäßige eingebaute Funktion __import__() aufgerufen. Andere Mechanismen zum Aufrufen des Importsystems (wie z. B. importlib.import_module()) können __import__() umgehen und 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. Wenn das benannte Modul nicht gefunden werden kann, wird ein ModuleNotFoundError ausgelöst. Python implementiert verschiedene Strategien, um bei Aufruf der Import-Mechanismen nach dem benannten Modul zu suchen. Diese Strategien können durch die Verwendung verschiedener Hooks, die in den folgenden Abschnitten beschrieben werden, modifiziert und erweitert werden.

Geändert in Version 3.3: Das Importsystem wurde aktualisiert, um die zweite Phase von PEP 302 vollständig zu implementieren. Es gibt keine impliziten Import-Mechanismen mehr – das gesamte Importsystem wird über sys.meta_path verfügbar gemacht. Zusätzlich wurde die native Unterstützung für Namensraum-Pakete implementiert (siehe PEP 420).

5.1. importlib

Das Modul importlib bietet eine umfangreiche API für die Interaktion mit dem Import-System. Zum Beispiel bietet importlib.import_module() eine empfohlene, einfachere API als die eingebaute Funktion __import__() zum Aufrufen der Import-Mechanismen. Weitere Details finden Sie in der Dokumentation der Bibliothek importlib.

5.2. Pakete

Python hat nur einen Typ von Modulobjekt, und alle Module sind von diesem Typ, unabhängig davon, ob das Modul in Python, C oder etwas anderem implementiert ist. Um Module zu organisieren und eine Namenshierarchie bereitzustellen, hat Python das Konzept der Pakete.

Sie können sich Pakete als Verzeichnisse in einem Dateisystem und Module als Dateien in Verzeichnissen vorstellen, aber nehmen Sie diese Analogie nicht zu wörtlich, da Pakete und Module nicht unbedingt aus dem Dateisystem stammen müssen. Für die Zwecke dieser Dokumentation verwenden wir diese praktische Analogie von Verzeichnissen und Dateien. Ähnlich wie Dateisystemverzeichnisse sind Pakete hierarchisch organisiert, und Pakete können ihrerseits Unterpakete sowie reguläre Module enthalten.

Es ist wichtig zu bedenken, dass alle Pakete Module sind, aber nicht alle Module Pakete sind. Oder anders ausgedrückt: Pakete sind nur eine spezielle Art von Modulen. Insbesondere jedes Modul, das ein __path__-Attribut enthält, gilt als Paket.

Alle Module haben einen Namen. Unterpaketnamen werden durch einen Punkt von ihrem übergeordneten Paketnamen getrennt, ähnlich der Standard-Attributzugriffssyntax von Python. So könnten Sie ein Paket namens email haben, das wiederum ein Unterpaket namens email.mime und darin ein Modul namens email.mime.text enthält.

5.2.1. Reguläre Pakete

Python definiert zwei Arten von Paketen: reguläre Pakete und Namensraum-Pakete. Reguläre Pakete sind traditionelle Pakete, wie sie in Python 3.2 und früher existierten. Ein reguläres Paket wird typischerweise als Verzeichnis mit einer Datei __init__.py implementiert. Wenn ein reguläres Paket importiert wird, wird diese Datei __init__.py implizit ausgeführt, und die von ihr definierten Objekte werden an Namen im Namensraum des Pakets gebunden. Die Datei __init__.py kann denselben Python-Code enthalten, den jedes andere Modul auch enthalten kann, und Python fügt dem Modul beim Import einige zusätzliche Attribute hinzu.

Beispielsweise definiert die folgende Dateisystemstruktur ein übergeordnetes Paket namens parent mit drei Unterpaketen:

parent/
    __init__.py
    one/
        __init__.py
    two/
        __init__.py
    three/
        __init__.py

Das Importieren von parent.one führt implizit parent/__init__.py und parent/one/__init__.py aus. Spätere Importe von parent.two oder parent.three führen entsprechend parent/two/__init__.py und parent/three/__init__.py aus.

5.2.2. Namensraum-Pakete

Ein Namensraum-Paket ist eine Zusammenstellung verschiedener Teile, wobei jeder Teil ein Unterpaket zum übergeordneten Paket beiträgt. Teile können sich an verschiedenen Orten im Dateisystem befinden. Teile können auch in Zip-Dateien, im Netzwerk oder an jedem anderen Ort gefunden werden, an dem Python während des Imports sucht. Namensraum-Pakete entsprechen möglicherweise nicht direkt Objekten im Dateisystem; sie können virtuelle Module sein, die keine konkrete Darstellung haben.

Namensraum-Pakete verwenden keine normale Liste für ihr __path__-Attribut. Stattdessen verwenden sie einen benutzerdefinierten iterierbaren Typ, der bei der nächsten Importanforderung innerhalb dieses Pakets automatisch eine neue Suche nach Paketteilen durchführt, wenn sich der Pfad ihres übergeordneten Pakets (oder sys.path für ein Top-Level-Paket) ändert.

Bei Namensraum-Paketen gibt es keine Datei parent/__init__.py. Tatsächlich können während der Importsuche mehrere parent-Verzeichnisse gefunden werden, wobei jedes von einem anderen Teil bereitgestellt wird. Daher ist parent/one möglicherweise nicht physisch neben parent/two angesiedelt. In diesem Fall erstellt Python ein Namensraum-Paket für das Top-Level-Paket parent, wann immer es oder eines seiner Unterpakete importiert wird.

Siehe auch PEP 420 für die Spezifikation von Namensraum-Paketen.

5.3. Suche

Um die Suche zu starten, benötigt Python den vollqualifizierten Namen des zu importierenden Moduls (oder Pakets, aber für die Zwecke dieser Diskussion ist der Unterschied unerheblich). Dieser Name kann aus verschiedenen Argumenten der import-Anweisung oder aus den Parametern der Funktionen importlib.import_module() oder __import__() stammen.

Dieser Name wird in verschiedenen Phasen der Importsuche verwendet und kann der Punkt-getrennte Pfad zu einem Untermodul sein, z. B. foo.bar.baz. In diesem Fall versucht Python zuerst, 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

Der erste Ort, der bei der Importsuche überprüft wird, ist sys.modules. Diese Zuordnung dient als Cache für alle zuvor importierten Module, 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 das entsprechende Modulobjekt als Wert.

Während des Imports wird der Modulname in sys.modules nachgeschlagen, und falls vorhanden, ist der zugehörige Wert das Modul, das den Import erfüllt, und der Vorgang ist abgeschlossen. Wenn der Wert jedoch None ist, wird ein ModuleNotFoundError ausgelöst. Wenn der Modulname fehlt, fährt Python mit der Suche nach dem Modul fort.

sys.modules ist beschreibbar. Das Löschen eines Schlüssels zerstört möglicherweise nicht das zugehörige Modul (da andere Module Referenzen darauf halten können), aber es macht den Cache-Eintrag für das benannte Modul ungültig, was dazu führt, dass Python bei seinem nächsten Import erneut nach dem benannten Modul sucht. Der Schlüssel kann auch auf None gesetzt werden, was bewirkt, dass der nächste Import des Moduls zu einem ModuleNotFoundError führt.

Seien Sie jedoch vorsichtig, denn wenn Sie eine Referenz auf das Modulobjekt behalten, dessen Cache-Eintrag in sys.modules ungültig machen und dann das benannte Modul erneut importieren, werden die beiden Modulobjekte *nicht* identisch sein. Im Gegensatz dazu verwendet importlib.reload() dasselbe Modulobjekt wieder und initialisiert einfach den Modulinhalt neu, indem der Code des Moduls erneut ausgeführt wird.

5.3.2. Finder und Loader

Wenn das benannte Modul nicht in sys.modules gefunden wird, wird das Importprotokoll von Python aufgerufen, um das Modul zu finden und zu laden. Dieses Protokoll besteht aus zwei konzeptionellen Objekten: Findern und Loadern. Die Aufgabe eines Finders ist es festzustellen, ob er das benannte Modul anhand der ihm bekannten Strategie 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 kennt sich mit dem Auffinden eingebauter Module aus, und der zweite kennt sich mit dem Auffinden von kompilierten Modulen aus. Ein dritter Standard-Finder durchsucht einen Import-Pfad nach Modulen. Der Import-Pfad ist eine Liste von Orten, die Dateisystempfade oder Zip-Dateien benennen können. Er kann auch erweitert werden, um nach beliebigen auffindbaren Ressourcen zu suchen, wie z. B. nach URLs.

Die Import-Mechanismen sind erweiterbar, so dass neue Finder hinzugefügt werden können, um den Bereich und Umfang der Modulsuchen zu erweitern.

Finder laden Module nicht tatsächlich. Wenn sie das benannte Modul finden können, geben sie einen *Modul-Spec* zurück, eine Kapselung der Import-bezogenen Informationen des Moduls, die die Import-Mechanismen dann beim Laden des Moduls verwenden.

Die folgenden Abschnitte beschreiben das Protokoll für Finder und Loader im Detail, einschließlich der Erstellung und Registrierung neuer Komponenten zur Erweiterung der Import-Mechanismen.

Geändert in Version 3.4: In früheren Versionen von Python gaben Finder direkt Loader zurück, während sie jetzt Modul-Specs zurückgeben, die Loader *enthalten*. Loader werden während des Imports immer noch verwendet, haben aber weniger Verantwortlichkeiten.

5.3.3. Import-Hooks

Das Import-System ist erweiterbar konzipiert; die primären Mechanismen dafü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, bevor irgendeine andere Importverarbeitung stattgefunden hat, abgesehen von der Nachschlageoperation im Cache sys.modules. Dies ermöglicht es Meta-Hooks, die Verarbeitung von sys.path, kompilierte Module oder sogar eingebaute Module zu überschreiben. Meta-Hooks werden registriert, indem neue Finder-Objekte zu sys.meta_path hinzugefügt werden, wie unten beschrieben.

Import-Pfad-Hooks werden als Teil der Verarbeitung von sys.path (oder package.__path__) aufgerufen, an dem Punkt, an dem ihr zugehöriger Pfad-Eintrag angetroffen wird. Import-Pfad-Hooks werden registriert, indem neue aufrufbare Objekte zu sys.path_hooks hinzugefügt werden, wie unten beschrieben.

5.3.4. Der Meta-Pfad

Wenn das benannte Modul nicht in sys.modules gefunden wird, durchsucht Python als nächstes sys.meta_path, das eine Liste von Meta-Pfad-Finder-Objekten enthält. Diese Finder werden abgefragt, um zu sehen, ob sie wissen, wie das benannte Modul zu handhaben ist. Meta-Pfad-Finder müssen eine Methode namens find_spec() implementieren, die drei Argumente entgegennimmt: einen Namen, einen Importpfad und (optional) ein Zielmodul. Der Meta-Pfad-Finder kann jede beliebige Strategie verwenden, um zu bestimmen, ob er das benannte Modul handhaben kann oder nicht.

Wenn der Meta-Pfad-Finder weiß, wie das benannte Modul zu handhaben ist, gibt er ein Spec-Objekt zurück. Wenn er das benannte Modul nicht handhaben kann, gibt er None zurück. Wenn die Verarbeitung von sys.meta_path das Ende seiner Liste erreicht, ohne ein Spec zurückzugeben, wird ein ModuleNotFoundError ausgelöst. Alle anderen ausgelösten Ausnahmen werden einfach weitergegeben und brechen den Importvorgang ab.

Die Methode find_spec() von Meta-Pfad-Findern wird mit zwei oder drei Argumenten aufgerufen. Das erste ist der vollqualifizierte Name des zu importierenden Moduls, z. B. foo.bar.baz. Das zweite Argument sind die Pfadeinträge, die für die Modulsuch verwendet werden sollen. Für Top-Level-Module ist das zweite Argument None, für Untermodule oder Unterpakete ist das zweite Argument jedoch der Wert des __path__-Attributs des übergeordneten Pakets. Wenn das entsprechende __path__-Attribut nicht abgerufen werden kann, wird ein ModuleNotFoundError ausgelöst. Das dritte Argument ist ein vorhandenes Modulobjekt, das später als Ziel des Ladens dient. Das Importsystem übergibt ein Zielmodul nur beim Neuladen.

Der Meta-Pfad kann für eine einzige Importanforderung mehrmals durchlaufen werden. Zum Beispiel wird beim Importieren von foo.bar.baz (unter der Annahme, dass keines der beteiligten Module bereits gecacht wurde) zuerst ein Top-Level-Import durchgeführt, der mpf.find_spec("foo", None, None) für jeden Meta-Pfad-Finder (mpf) aufruft. Nachdem foo importiert wurde, wird foo.bar durch einmaliges Durchlaufen des Meta-Pfads ein zweites Mal importiert, indem 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-Pfad-Finder unterstützen nur Top-Level-Importe. Diese Importer geben immer None zurück, wenn etwas anderes als None als zweites Argument übergeben wird.

Das Standard- sys.meta_path von Python enthält drei Meta-Pfad-Finder: einen, der weiß, wie eingebaute Module importiert werden; einen, der weiß, wie kompilierte Module importiert werden; und einen, der weiß, wie Module aus einem Import-Pfad importiert werden (d. h. den pfadbasierten Finder).

Geändert in Version 3.4: Die Methode find_spec() von Meta-Pfad-Findern ersetzte find_module(), die nun veraltet ist. Obwohl sie weiterhin unverändert funktioniert, wird die Import-Mechanik sie nur versuchen, wenn der Finder find_spec() nicht implementiert.

Geändert in Version 3.10: Die Verwendung von find_module() durch das Importsystem löst nun ImportWarning aus.

Geändert in Version 3.12: find_module() wurde entfernt. Verwenden Sie stattdessen find_spec().

5.4. Laden

Wenn und sobald ein Modul-Spec gefunden wird, verwendet die Import-Mechanik ihn (und den darin enthaltenen Loader) beim Laden des Moduls. Hier ist eine Annäherung dessen, was während des Ladevorgangs des Imports geschieht:

module = None
if spec.loader is not None and hasattr(spec.loader, 'create_module'):
    # It is assumed 'exec_module' will also be defined on the loader.
    module = spec.loader.create_module(spec)
if module is None:
    module = ModuleType(spec.name)
# The import-related module attributes get set here:
_init_module_attrs(spec, module)

if spec.loader is None:
    # unsupported
    raise ImportError
if spec.origin is None and spec.submodule_search_locations is not None:
    # namespace package
    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]

Beachten Sie die folgenden Details:

  • Wenn bereits ein Modulobjekt mit dem gegebenen Namen in sys.modules vorhanden ist, hat der Import es bereits zurückgegeben.

  • Das Modul wird in sys.modules vorhanden sein, bevor der Loader den Modulcode ausführt. Dies ist entscheidend, da der Modulcode sich selbst importieren kann (direkt oder indirekt); ihn vorher in sys.modules aufzunehmen, verhindert im schlimmsten Fall unendliche Rekursion und im besten Fall mehrfaches Laden.

  • Wenn das Laden fehlschlägt, wird das fehlerhafte Modul – und nur das fehlerhafte Modul – aus sys.modules entfernt. Jedes Modul, das sich bereits im Cache von sys.modules befand, und jedes Modul, das als Nebeneffekt erfolgreich geladen wurde, muss im Cache verbleiben. Dies steht im Gegensatz zum erneuten Laden, bei dem selbst das fehlerhafte Modul in sys.modules verbleibt.

  • Nachdem das Modul erstellt wurde, aber vor der Ausführung, setzt die Import-Maschinerie die importbezogenen Modulattribute („_init_module_attrs“ im obigen Pseudocode-Beispiel), wie in einem späteren Abschnitt zusammengefasst.

  • Die Modulausführung ist der entscheidende Moment des Ladens, in dem der Namensraum des Moduls gefüllt wird. Die Ausführung wird vollständig an den Loader delegiert, der entscheiden kann, was und wie gefüllt wird.

  • Das während des Ladens erstellte und an exec_module() übergebene Modul ist möglicherweise nicht dasjenige, das am Ende des Imports zurückgegeben wird [2].

Geändert in Version 3.4: Das Importsystem hat die Boilerplate-Verantwortlichkeiten von Loadern übernommen. Diese wurden zuvor von der importlib.abc.Loader.load_module()-Methode übernommen.

5.4.1. Loader

Modul-Loader stellen die kritische Funktion des Ladens bereit: die Modulausführung. Die Import-Maschinerie ruft die Methode importlib.abc.Loader.exec_module() mit einem einzigen Argument auf, dem auszuführenden Modulobjekt. Jeder von exec_module() zurückgegebene Wert wird ignoriert.

Loader müssen die folgenden Anforderungen erfüllen

  • Wenn das Modul ein Python-Modul ist (im Gegensatz zu einem integrierten Modul oder einer dynamisch geladenen Erweiterung), sollte der Loader den Code des Moduls im globalen Namensraum des Moduls (module.__dict__) ausführen.

  • Wenn der Loader das Modul nicht ausführen kann, sollte er einen ImportError auslösen, obwohl jede andere Ausnahme, die während exec_module() ausgelöst wird, weitergegeben wird.

In vielen Fällen können Finder und Loader dasselbe Objekt sein; in solchen Fällen gibt die Methode find_spec() einfach eine Spezifikation mit dem Loader zurück, der auf self gesetzt ist.

Modul-Loader können sich dafür entscheiden, das Modulobjekt während des Ladens zu erstellen, indem sie eine Methode create_module() implementieren. Diese nimmt ein Argument, die Modulspezifikation, entgegen und gibt das neue Modulobjekt zurück, das während des Ladens verwendet werden soll. create_module() muss keine Attribute am Modulobjekt setzen. Wenn die Methode None zurückgibt, erstellt die Import-Maschinerie das neue Modul selbst.

Hinzugefügt in Version 3.4: Die Methode create_module() von Loadern.

Geändert in Version 3.4: Die Methode load_module() wurde durch exec_module() ersetzt und die Import-Maschinerie hat alle Boilerplate-Verantwortlichkeiten des Ladens übernommen.

Zur Kompatibilität mit bestehenden Loadern verwendet die Import-Maschinerie die Methode load_module() von Loadern, wenn diese existiert und der Loader nicht auch exec_module() implementiert. load_module() ist jedoch veraltet und Loader sollten stattdessen exec_module() implementieren.

Die Methode load_module() muss alle oben beschriebenen Boilerplate-Ladefunktionen implementieren, zusätzlich zur Ausführung des Moduls. Alle gleichen Einschränkungen gelten, mit einigen zusätzlichen Klarstellungen

  • Wenn ein Modulobjekt mit dem gegebenen Namen bereits in sys.modules vorhanden ist, muss der Loader dieses vorhandene Modul verwenden. (Andernfalls funktioniert importlib.reload() nicht korrekt.) Wenn das benannte Modul nicht in sys.modules existiert, muss der Loader ein neues Modulobjekt erstellen und es zu sys.modules hinzufügen.

  • Das Modul *muss* in sys.modules vorhanden sein, bevor der Loader den Modulcode ausführt, um unbegrenzte Rekursion oder mehrfaches Laden zu verhindern.

  • Wenn das Laden fehlschlägt, muss der Loader alle Module entfernen, die er in sys.modules eingefügt hat, aber er muss *nur* das fehlerhafte Modul/die fehlerhaften Module entfernen und nur, wenn der Loader das/die Modul(e) explizit selbst geladen hat.

Geändert in Version 3.5: Eine DeprecationWarning wird ausgelöst, wenn exec_module() definiert ist, aber create_module() nicht.

Geändert in Version 3.6: Ein ImportError wird ausgelöst, wenn exec_module() definiert ist, aber create_module() nicht.

Geändert in Version 3.10: Die Verwendung von load_module() löst eine ImportWarning aus.

5.4.2. Untermodule

Wenn ein Untermodul mit einem beliebigen Mechanismus geladen wird (z. B. importlib-APIs, die import- oder import-from-Anweisungen oder das integrierte __import__()), wird eine Bindung im Namensraum des übergeordneten Moduls zum Untermodulobjekt platziert. Wenn beispielsweise das Paket spam ein Untermodul foo hat, hat spam nach dem Importieren von spam.foo ein Attribut foo, das an das Untermodul gebunden ist. Nehmen wir an, Sie haben die folgende Verzeichnisstruktur

spam/
    __init__.py
    foo.py

und spam/__init__.py enthält die folgende Zeile

from .foo import Foo

Dann platziert die Ausführung des Folgenden Namensbindungen für foo und Foo im Modul spam

>>> import spam
>>> spam.foo
<module 'spam.foo' from '/tmp/imports/spam/foo.py'>
>>> spam.Foo
<class 'spam.foo.Foo'>

Angesichts der vertrauten Namensbindungsregeln von Python mag dies überraschend erscheinen, ist aber tatsächlich ein grundlegendes Merkmal des Importsystems. Die Invariante, die gilt, ist, dass wenn Sie sys.modules['spam'] und sys.modules['spam.foo'] (wie Sie es nach dem obigen Import hätten) haben, das letztere als Attribut foo des ersteren erscheinen muss.

5.4.3. Modulspezifikationen

Die Import-Maschinerie verwendet eine Vielzahl von Informationen über jedes Modul während des Imports, insbesondere vor dem Laden. Die meisten Informationen sind für alle Module gleich. Der Zweck einer Modulspezifikation ist es, diese importbezogenen Informationen pro Modul zu kapseln.

Die Verwendung einer Spezifikation während des Imports ermöglicht den Transfer von Zuständen zwischen Importsystemkomponenten, z. B. zwischen dem Finder, der die Modulspezifikation erstellt, und dem Loader, der sie ausführt. Am wichtigsten ist, dass sie es der Import-Maschinerie ermöglicht, die Boilerplate-Operationen des Ladens durchzuführen, während ohne Modulspezifikation der Loader diese Verantwortung hatte.

Die Modulspezifikation wird als module.__spec__ freigelegt. Das Setzen von __spec__ gilt gleichermaßen für Module, die während des Interpreterstarts initialisiert werden. Die einzige Ausnahme ist __main__, wo __spec__ in einigen Fällen auf None gesetzt wird siehe Hauptspezifikation.

Einzelheiten zum Inhalt der Modulspezifikation finden Sie unter ModuleSpec.

Hinzugefügt in Version 3.4.

5.4.4. __path__-Attribute bei Modulen

Das Attribut __path__ sollte eine (möglicherweise leere) Sequenz von Zeichenketten sein, die die Speicherorte aufzählt, an denen die Untermodule des Pakets gefunden werden. Per Definition ist ein Modul ein Paket, wenn es ein __path__-Attribut hat.

Das Attribut __path__ eines Pakets wird während des Imports seiner Unterpakete verwendet. Innerhalb der Import-Maschinerie funktioniert es ähnlich wie sys.path, d. h. es stellt eine Liste von Speicherorten bereit, an denen während des Imports nach Modulen gesucht wird. Allerdings ist __path__ typischerweise viel eingeschränkter als sys.path.

Die gleichen Regeln, die für sys.path gelten, gelten auch für das __path__ eines Pakets. sys.path_hooks (unten beschrieben) werden beim Durchlaufen des __path__ eines Pakets konsultiert.

Die Datei __init__.py eines Pakets kann das __path__-Attribut des Pakets setzen oder ändern, und dies war typischerweise die Art und Weise, wie Namensraum-Pakete vor PEP 420 implementiert wurden. Mit der Einführung von PEP 420 müssen Namensraum-Pakete keine __init__.py-Dateien mehr enthalten, die nur Code zur Manipulation von __path__ enthalten; die Import-Maschinerie setzt __path__ automatisch korrekt für den Namensraum-Paket.

5.4.5. Modul-Repr

Standardmäßig haben alle Module eine verwendbare Repräsentation (repr). Abhängig von den oben gesetzten Attributen und in der Modulspezifikation können Sie die Repräsentation von Modulobjekten expliziter steuern.

Wenn das Modul eine Spezifikation (__spec__) hat, versucht die Import-Maschinerie, eine Repräsentation daraus zu generieren. Wenn dies fehlschlägt oder keine Spezifikation vorhanden ist, erstellt das Importsystem eine Standardrepräsentation unter Verwendung der verfügbaren Informationen über das Modul. Es versucht, module.__name__, module.__file__ und module.__loader__ als Eingabe für die Repräsentation zu verwenden, mit Standardwerten für fehlende Informationen.

Hier sind die genauen Regeln, die verwendet werden

  • Wenn das Modul ein Attribut __spec__ hat, werden die Informationen in der Spezifikation zur Generierung der Repräsentation verwendet. Die Attribute „name“, „loader“, „origin“ und „has_location“ werden berücksichtigt.

  • Wenn das Modul ein Attribut __file__ hat, wird dieses als Teil der Modulrepräsentation verwendet.

  • Wenn das Modul kein __file__, aber ein __loader__ hat, das nicht None ist, dann wird die Repräsentation des Loaders als Teil der Modulrepräsentation verwendet.

  • Andernfalls wird nur der __name__ des Moduls in der Repräsentation verwendet.

Geändert in Version 3.12: Die Verwendung von module_repr(), die seit Python 3.4 als veraltet galt, wurde in Python 3.12 entfernt und wird nicht mehr während der Auflösung der Modulrepräsentation aufgerufen.

5.4.6. Gültigkeitsprüfung von kompiliertem Bytecode

Bevor Python kompilierten Bytecode aus einer .pyc-Datei lädt, prüft es, ob der Cache mit der Quellcodedatei .py aktuell ist. Standardmäßig geschieht dies, indem Python beim Schreiben der Cache-Datei den letzten Änderungszeitstempel und die Größe der Quelldatei darin speichert. Zur Laufzeit validiert das Importsystem die Cache-Datei, indem es die gespeicherten Metadaten in der Cache-Datei mit den Metadaten der Quelle abgleicht.

Python unterstützt auch „hash-basierte“ Cache-Dateien, die einen Hash des Inhalts der Quelldatei anstelle ihrer Metadaten speichern. Es gibt zwei Varianten von hash-basierten .pyc-Dateien: geprüft und ungeprüft. Bei geprüften hash-basierten .pyc-Dateien validiert Python die Cache-Datei, indem es die Quelldatei hasht und den resultierenden Hash mit dem Hash in der Cache-Datei vergleicht. Wenn eine geprüfte hash-basierte Cache-Datei als ungültig befunden wird, 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, wenn sie existiert. Das Validierungsverhalten von hash-basierten .pyc-Dateien kann mit der Option --check-hash-based-pycs überschrieben werden.

Geändert in Version 3.7: Hash-basierte .pyc-Dateien hinzugefügt. Zuvor unterstützte Python nur zeitstempelbasierte Invalidierung von Bytecode-Caches.

5.5. Der Pfadbasierte Finder

Wie bereits erwähnt, wird Python mit mehreren Standard-Meta-Pfad-Findern geliefert. Einer davon, der sogenannte pfadbasierte Finder (PathFinder), durchsucht einen Importpfad, der eine Liste von Pfadeinträgen enthält. Jeder Pfadeintrag benennt einen Speicherort, an dem nach Modulen gesucht werden soll.

Der pfadbasierte Finder selbst weiß nicht, wie er etwas importieren soll. Stattdessen durchläuft er die einzelnen Pfadeinträge und ordnet jedem davon einen Pfadeintrag-Finder zu, der weiß, wie diese spezielle Art von Pfad zu behandeln ist.

Die Standardmenge von Pfadeintrag-Findern implementiert die gesamte Semantik für das Finden von Modulen auf dem Dateisystem und behandelt spezielle Dateitypen wie Python-Quellcode (.py-Dateien), Python-Bytecode (.pyc-Dateien) und Shared Libraries (z. B. .so-Dateien). Wenn sie vom Modul zipimport in der Standardbibliothek unterstützt werden, behandeln die Standard-Pfadeintrag-Finder auch das Laden all dieser Dateitypen (außer Shared Libraries) aus Zip-Dateien.

Pfadeinträge müssen nicht auf Dateisystemspeicherorte beschränkt sein. Sie können auf URLs, Datenbankabfragen oder andere Speicherorte verweisen, die als Zeichenkette angegeben werden können.

Der pfadbasierte Finder stellt zusätzliche Hooks und Protokolle bereit, damit Sie die Arten von durchsuchbaren Pfadeinträgen erweitern und anpassen können. Wenn Sie beispielsweise Pfadeinträge als Netzwerk-URLs unterstützen wollten, könnten Sie einen Hook schreiben, der HTTP-Semantik implementiert, um Module im Web zu finden. Dieser Hook (ein aufrufbares Objekt) würde einen Pfadeintrag-Finder zurückgeben, der das unten beschriebene Protokoll unterstützt, das dann verwendet würde, um einen Loader für das Modul aus dem Web zu erhalten.

Ein Wort der Warnung: Dieser Abschnitt und der vorherige verwenden beide den Begriff *Finder*, unterscheiden sie aber durch die Begriffe Meta-Pfad-Finder und Pfadeintrag-Finder. Diese beiden Arten von Findern sind sich sehr ähnlich, unterstützen ähnliche Protokolle und funktionieren während des Importprozesses auf ähnliche Weise, aber es ist wichtig zu bedenken, dass sie subtil unterschiedlich sind. Insbesondere Meta-Pfad-Finder operieren am Anfang des Importprozesses, wie sie durch die sys.meta_path-Durchlauf getriggert werden.

Im Gegensatz dazu sind Pfadeintrag-Finder in gewisser Weise ein Implementierungsdetail des pfadbasierten Finders, und tatsächlich, wenn der pfadbasierte Finder aus sys.meta_path entfernt würde, würden keine der Pfadeintrag-Finder-Semantiken aufgerufen.

5.5.1. Pfadeintrag-Finder

Der pfadbasierte Finder ist dafür verantwortlich, Python-Module und Pakete zu finden und zu laden, deren Speicherort mit einer Zeichenketten-Pfadangabe angegeben ist. Die meisten Pfadeinträge benennen Speicherorte im Dateisystem, müssen aber nicht darauf beschränkt sein.

Als Meta-Pfad-Finder implementiert der pfadbasierte Finder das zuvor beschriebene find_spec()-Protokoll, exponiert aber zusätzliche Hooks, die verwendet werden können, um anzupassen, wie Module aus dem Importpfad gefunden und geladen werden.

Drei Variablen werden vom pfadbasierten Finder verwendet: sys.path, sys.path_hooks und sys.path_importer_cache. Die __path__-Attribute von Paketobjekten werden ebenfalls verwendet. Diese bieten zusätzliche Möglichkeiten zur Anpassung der Import-Maschinerie.

sys.path enthält eine Liste von Zeichenketten, die Suchorte für Module und Pakete angeben. Sie wird aus der Umgebungsvariable PYTHONPATH und verschiedenen anderen installations- und implementierungsspezifischen Standardwerten initialisiert. Einträge in sys.path können Verzeichnisse auf dem Dateisystem, Zip-Dateien und potenziell andere „Speicherorte“ (siehe Modul site) bezeichnen, die nach Modulen durchsucht werden sollten, wie URLs oder Datenbankabfragen. Nur Zeichenketten sollten in sys.path enthalten sein; alle anderen Datentypen werden ignoriert.

Der pfadbasierte Finder ist ein Meta-Pfad-Finder, daher beginnt die Import-Maschinerie die Suche im Importpfad durch Aufruf der Methode find_spec() des pfadbasierten Finders, wie zuvor beschrieben. Wenn das `path`-Argument zu find_spec() gegeben wird, ist es eine Liste von Zeichenkettenpfaden zum Durchlaufen – typischerweise das __path__-Attribut eines Pakets für einen Import innerhalb dieses Pakets. Wenn das `path`-Argument `None` ist, deutet dies auf einen Top-Level-Import hin und sys.path wird verwendet.

Der pfadbasierte Finder iteriert über jeden Eintrag im Suchpfad und sucht für jeden davon nach einem geeigneten Pfadeintrag-Finder (PathEntryFinder) für den Pfadeintrag. Da dies ein kostspieliger Vorgang sein kann (z. B. kann es zu stat()-Aufruf-Overheads für diese Suche kommen), unterhält der pfadbasierte Finder einen Cache, der Pfadeinträge und Pfadeintrag-Finder abbildet. 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 kostspielige Suche nach dem Pfadeintrag-Finder für einen bestimmten Pfad-Eintrag-Speicherort nur einmal durchgeführt werden. Benutzercode kann Cache-Einträge aus sys.path_importer_cache entfernen und zwingt den pfadbasierten Finder, die Pfadeintragssuche erneut durchzuführen.

Wenn der Pfadeintrag nicht im Cache vorhanden ist, durchläuft der pfadbasierte Finder jeden aufrufbaren Eintrag in sys.path_hooks. Jeder der Pfad-Eintrag-Hooks in dieser Liste wird mit einem einzigen Argument aufgerufen, dem zu durchsuchenden Pfadeintrag. Dieser Aufrufbare kann entweder einen Pfad-Eintrag-Finder zurückgeben, der den Pfadeintrag verarbeiten kann, oder er kann ImportError auslösen. Ein ImportError wird vom pfadbasierten Finder verwendet, um zu signalisieren, dass der Hook keinen Pfad-Eintrag-Finder für diesen Pfad-Eintrag finden kann. Die Ausnahme wird ignoriert und die Iteration des Import-Pfads wird fortgesetzt. Der Hook sollte entweder ein Zeichenketten- oder ein Bytes-Objekt erwarten; die Kodierung von Bytes-Objekten liegt im Ermessen des Hooks (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 ImportError auslösen.

Wenn die Iteration von sys.path_hooks endet, ohne dass ein Pfad-Eintrag-Finder zurückgegeben wurde, dann 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 anzeigt, dass dieser Meta-Pfad-Finder das Modul nicht finden konnte.

Wenn ein Pfad-Eintrag-Finder von einem der aufrufbaren Pfad-Eintrag-Hooks in sys.path_hooks zurückgegeben *wird*, dann wird das folgende Protokoll verwendet, um den Finder nach einem Modulspezifikations-Objekt (spec) zu fragen, das dann beim Laden des Moduls verwendet wird.

Das aktuelle Arbeitsverzeichnis – bezeichnet durch einen leeren String – wird etwas anders behandelt als andere Einträge in sys.path. Erstens, wenn das aktuelle Arbeitsverzeichnis nicht ermittelt werden kann oder nicht existiert, wird kein Wert in sys.path_importer_cache gespeichert. Zweitens wird der Wert für das aktuelle Arbeitsverzeichnis für jede Modulsuchung frisch nachgeschlagen. Drittens ist der für sys.path_importer_cache verwendete und von importlib.machinery.PathFinder.find_spec() zurückgegebene Pfad das tatsächliche aktuelle Arbeitsverzeichnis und nicht der leere String.

5.5.2. Protokoll des Pfad-Eintrag-Finders

Um den Import von Modulen und initialisierten Paketen zu unterstützen und auch Teile zu Namespace-Paketen beizutragen, müssen Pfad-Eintrag-Finder die Methode find_spec() implementieren.

find_spec() nimmt zwei Argumente entgegen: den vollständig qualifizierten Namen des zu importierenden Moduls und das (optionale) Zielmodul. find_spec() gibt eine vollständig aufgefüllte Spezifikation (spec) für das Modul zurück. Diese Spezifikation hat immer einen gesetzten „loader“ (mit einer Ausnahme).

Um der Importmaschine anzuzeigen, dass die Spezifikation einen Namespace-Teil repräsentiert, setzt der Pfad-Eintrag-Finder submodule_search_locations auf eine Liste, die den Teil enthält.

Geändert in Version 3.4: find_spec() hat find_loader() und find_module() ersetzt, die beide jetzt veraltet sind, aber verwendet werden, wenn find_spec() nicht definiert ist.

Ältere Pfad-Eintrag-Finder können eine dieser beiden veralteten Methoden anstelle von find_spec() implementieren. Die Methoden werden aus Gründen der Abwärtskompatibilität weiterhin berücksichtigt. Wenn jedoch find_spec() auf dem Pfad-Eintrag-Finder implementiert ist, werden die Legacy-Methoden ignoriert.

find_loader() nimmt ein Argument entgegen, den vollständig qualifizierten Namen des zu importierenden Moduls. find_loader() gibt ein 2-Tupel zurück, bei dem das erste Element der Loader und das zweite Element ein Namespace-Teil ist.

Zurückwärtskompatibilität mit anderen Implementierungen des Importprotokolls: Viele Pfad-Eintrag-Finder unterstützen auch dieselbe, traditionelle Methode find_module(), die Meta-Pfad-Finder unterstützen. Pfad-Eintrag-Finder find_module() Methoden werden jedoch niemals mit einem path-Argument aufgerufen (sie sollen die entsprechenden Pfadinformationen aus dem ersten Aufruf des Pfad-Hooks aufzeichnen).

Die Methode find_module() auf Pfad-Eintrag-Findern ist veraltet, da sie es dem Pfad-Eintrag-Finder nicht ermöglicht, Teile zu Namespace-Paketen beizutragen. Wenn sowohl find_loader() als auch find_module() auf einem Pfad-Eintrag-Finder vorhanden sind, ruft das Importsystem immer find_loader() bevorzugt gegenüber find_module() auf.

Geändert in Version 3.10: Aufrufe von find_module() und find_loader() durch das Importsystem lösen ImportWarning aus.

Geändert in Version 3.12: find_module() und find_loader() wurden entfernt.

5.6. Ersetzen des Standard-Importsystems

Der zuverlässigste Mechanismus zum Ersetzen des gesamten Importsystems ist das Löschen des Standardinhalts von sys.meta_path und dessen vollständiger Ersetzung durch einen benutzerdefinierten Meta-Pfad-Hook.

Wenn es akzeptabel ist, nur das Verhalten von Importanweisungen zu ändern, ohne andere APIs zu beeinflussen, die auf das Importsystem zugreifen, dann kann das Ersetzen der integrierten Funktion __import__() ausreichend sein. Diese Technik kann auch auf Modulebene angewendet werden, um das Verhalten von Importanweisungen nur innerhalb dieses Moduls zu ändern.

Um den Import bestimmter Module von einem Hook frühzeitig im Meta-Pfad (anstatt das Standard-Importsystem vollständig zu deaktivieren) selektiv zu verhindern, reicht es aus, ModuleNotFoundError direkt von find_spec() auszulösen, anstatt None zurückzugeben. Letzteres zeigt an, dass die Meta-Pfad-Suche fortgesetzt werden soll, während das Auslösen einer Ausnahme diese sofort beendet.

5.7. Paket-relative Importe

Relative Importe verwenden führende Punkte. Ein einzelner führender Punkt kennzeichnet einen relativen Import, beginnend mit dem aktuellen Paket. Zwei oder mehr führende Punkte kennzeichnen einen relativen Import zu den Elternteilen des aktuellen Pakets, ein Level pro Punkt nach dem ersten. Zum Beispiel, gegeben das folgende Paketlayout

package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
        moduleY.py
    subpackage2/
        __init__.py
        moduleZ.py
    moduleA.py

Entweder in subpackage1/moduleX.py oder subpackage1/__init__.py sind die folgenden relative 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, aber relative Importe dürfen nur die zweite Form verwenden; der Grund dafür ist, dass

import XXX.YYY.ZZZ

sollte XXX.YYY.ZZZ als verwendbarer Ausdruck verfügbar machen, aber .moduleY ist kein gültiger Ausdruck.

5.8. Spezielle Überlegungen zu __main__

Das Modul __main__ ist ein Sonderfall im Verhältnis zum Python-Importsystem. Wie anderswo erwähnt, wird das Modul __main__ direkt beim Start des Interpreters initialisiert, ähnlich wie sys und builtins. Im Gegensatz zu diesen beiden qualifiziert es sich jedoch nicht streng 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__

Abhängig davon, wie __main__ initialisiert wird, wird __main__.__spec__ entsprechend gesetzt oder auf None.

Wenn Python mit der Option -m gestartet wird, wird __spec__ auf die Modulspezifikation des entsprechenden Moduls oder Pakets gesetzt. __spec__ wird auch aufgefüllt, wenn das Modul __main__ als Teil der Ausführung eines Verzeichnisses, einer Zip-Datei oder eines anderen sys.path-Eintrags geladen wird.

In den übrigen Fällen wird __main__.__spec__ auf None gesetzt, da der Code, der zum Auffüllen von __main__ verwendet wird, nicht direkt einem importierbaren Modul entspricht.

  • interaktive Eingabeaufforderung

  • Option -c

  • Ausführung von stdin

  • Ausführung direkt aus einer Quell- oder Bytecode-Datei

Beachten Sie, dass __main__.__spec__ im letzten Fall immer None ist, *auch wenn* die Datei technisch gesehen direkt als Modul importiert werden könnte. Verwenden Sie den Schalter -m, wenn Sie gültige Modulmetadaten in __main__ wünschen.

Beachten Sie auch, dass selbst wenn __main__ einem importierbaren Modul entspricht und __main__.__spec__ entsprechend gesetzt ist, sie immer noch als *unterschiedliche* Module betrachtet werden. Dies liegt daran, dass Blöcke, die durch if __name__ == "__main__": Checks geschützt sind, nur ausgeführt werden, wenn das Modul zur Auffüllung des __main__-Namensraums verwendet wird, und nicht während des normalen Imports.

5.9. Referenzen

Die Importmaschine hat sich seit den frühen Tagen von Python erheblich weiterentwickelt. Die ursprüngliche Spezifikation für Pakete ist weiterhin lesbar, obwohl sich einige Details seit der Erstellung dieses Dokuments geändert haben.

Die ursprüngliche Spezifikation für sys.meta_path war PEP 302, mit einer nachfolgenden Erweiterung in PEP 420.

PEP 420 führte Namespace-Pakete für Python 3.3 ein. PEP 420 führte auch das Protokoll find_loader() als Alternative zu find_module() ein.

PEP 366 beschreibt die Hinzufügung des Attributs __package__ für explizite relative Importe in Hauptmodulen.

PEP 328 führte absolute und explizit relative Importe ein und schlug zunächst __name__ für die Semantik vor, die PEP 366 schließlich für __package__ spezifizieren würde.

PEP 338 definiert die Ausführung von Modulen als Skripte.

PEP 451 fügt die Kapselung von pro-Modul-Importzuständen in Spec-Objekte hinzu. Es lagert auch die meisten Boilerplate-Aufgaben von Loadern zurück auf die Importmaschine. Diese Änderungen ermöglichen die Veralterung mehrerer APIs im Importsystem und auch die Hinzufügung neuer Methoden zu Findern und Loadern.

Fußnoten