Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

PEP 451 – Ein ModuleSpec-Typ für das Import-System

Autor:
Eric Snow <ericsnowcurrently at gmail.com>
BDFL-Delegate:
Brett Cannon <brett at python.org>, Alyssa Coghlan <ncoghlan at gmail.com>
Discussions-To:
Import-SIG Liste
Status:
Final
Typ:
Standards Track
Erstellt:
08-Aug-2013
Python-Version:
3.4
Post-History:
08-Aug-2013, 28-Aug-2013, 18-Sep-2013, 24-Sep-2013, 04-Okt-2013
Resolution:
Python-Dev Nachricht

Inhaltsverzeichnis

Zusammenfassung

Diese PEP schlägt die Hinzufügung einer neuen Klasse zu importlib.machinery namens „ModuleSpec“ vor. Sie wird alle importbezogenen Informationen bereitstellen, die zum Laden eines Moduls benötigt werden, und wird verfügbar sein, ohne dass das Modul zuerst geladen werden muss. Finder werden direkt eine Modulspezifikation anstelle eines Loaders bereitstellen (den sie weiterhin indirekt bereitstellen werden). Die Import-Maschinerie wird angepasst, um Modulspezifikationen zu nutzen, einschließlich ihrer Verwendung zum Laden von Modulen.

Begriffe und Konzepte

Die in diesem Vorschlag enthaltenen Änderungen bieten die Gelegenheit, mehrere bestehende Begriffe und Konzepte zu verdeutlichen, während sie derzeit (unglücklicherweise) mehrdeutig sind. Neue Konzepte werden ebenfalls in diesem Vorschlag eingeführt. Schließlich lohnt es sich, einige andere bestehende Begriffe zu erklären, mit denen die Leute möglicherweise nicht so vertraut sind. Zur Kontextualisierung finden Sie hier eine kurze Zusammenfassung aller drei Gruppen von Begriffen und Konzepten. Eine detailliertere Erklärung des Import-Systems finden Sie unter [2].

name

In diesem Vorschlag bezieht sich der „Name“ eines Moduls auf seinen vollständig qualifizierten Namen, d. h. den vollständig qualifizierten Namen des übergeordneten Moduls (falls vorhanden), verbunden mit dem einfachen Namen des Moduls durch einen Punkt.

Finder

Ein „Finder“ ist ein Objekt, das den Loader identifiziert, den das Import-System zum Laden eines Moduls verwenden soll. Derzeit wird dies durch Aufruf der Methode `find_module()` des Finders erreicht, die den Loader zurückgibt.

Finder sind ausschließlich für die Bereitstellung des Loaders verantwortlich, was sie über ihre `find_module()`-Methode tun. Das Import-System verwendet dann diesen Loader, um das Modul zu laden.

Loader

Ein „Loader“ ist ein Objekt, das beim Import zum Laden eines Moduls verwendet wird. Derzeit geschieht dies durch Aufruf der Methode `load_module()` des Loaders. Ein Loader kann auch APIs zur Abfrage von Informationen über die Module, die er laden kann, sowie über Daten aus Quellen, die mit einem solchen Modul verknüpft sind, bereitstellen.

Derzeit sind Loader (über `load_module()`) für bestimmte Boilerplate-Code- und Import-bezogene Operationen verantwortlich. Diese sind:

  1. Durchführung einiger (modulbezogener) Validierungen
  2. Erstellung des Modulobjekts
  3. Festlegen von Import-bezogenen Attributen für das Modul
  4. „Registrierung“ des Moduls in `sys.modules`
  5. Ausführung des Moduls
  6. Aufräumarbeiten bei einem Fehler während des Ladens des Moduls

All dies geschieht während des Aufrufs von `Loader.load_module()` durch das Import-System.

Ursprung

Dies ist ein neuer Begriff und ein neues Konzept. Die Idee dazu existiert bereits subtil im Import-System, aber dieser Vorschlag macht das Konzept explizit.

„Ursprung“ im Importkontext bedeutet das System (oder die Ressource innerhalb eines Systems), aus dem ein Modul stammt. Für die Zwecke dieses Vorschlags ist „Ursprung“ auch eine Zeichenkette, die eine solche Ressource oder ein solches System identifiziert. „Ursprung“ ist für alle Module anwendbar.

Zum Beispiel ist der Ursprung von eingebauten und gefrorenen Modulen der Interpreter selbst. Das Import-System identifiziert diesen Ursprung bereits als „built-in“ bzw. „frozen“. Dies wird im folgenden Modul-`repr` demonstriert: „<module ‘sys’ (built-in)>“.

Tatsächlich ist das Modul-`repr` bereits ein relativ zuverlässiger, wenn auch impliziter, Indikator für den Ursprung eines Moduls. Andere Module zeigen ihren Ursprung auch auf andere Weise an, wie im Eintrag für „Ort“ beschrieben.

Es liegt am Loader zu entscheiden, wie der Ursprung eines Moduls interpretiert und verwendet wird, falls überhaupt.

Ort

Dies ist ein neuer Begriff. Das Konzept existiert jedoch bereits klar im Import-System, wie es mit den Attributen `__file__` und `__path__` von Modulen sowie mit dem Namen/Begriff „Pfad“ an anderer Stelle verbunden ist.

Ein „Ort“ ist eine Ressource oder ein „Platz“ im Gegensatz zu einem System im Allgemeinen, von dem aus ein Modul geladen wird. Er qualifiziert als „Ursprung“. Beispiele für Orte sind Dateisystempfade und URLs. Ein Ort wird durch den Namen der Ressource identifiziert, identifiziert aber möglicherweise nicht zwangsläufig das System, zu dem die Ressource gehört. In solchen Fällen müsste der Loader das System selbst identifizieren.

Im Gegensatz zu anderen Arten von Modulursprüngen kann ein Ort vom Loader nicht allein anhand des Modulnamens abgeleitet werden. Stattdessen muss dem Loader eine Zeichenkette zur Identifizierung des Ortes zur Verfügung gestellt werden, normalerweise vom Finder, der den Loader generiert. Der Loader verwendet diese Informationen dann, um die Ressource zu finden, aus der er das Modul laden wird. Theoretisch könnte man das Modul an einem bestimmten Ort unter verschiedenen Namen laden.

Die häufigsten Beispiele für Orte im Import-System sind die Dateien, aus denen Quellcode- und Erweiterungsmodule geladen werden. Für diese Module wird der Ort durch die Zeichenkette im Attribut `__file__` identifiziert. Obwohl `__file__` für einige Module (z.B. gezippt) nicht besonders genau ist, ist es derzeit die einzige Möglichkeit, wie das Import-System anzeigt, dass ein Modul einen Ort hat.

Ein Modul mit einem Ort kann als „lokalisierbar“ bezeichnet werden.

Cache

Das Import-System speichert kompilierte Module im `__pycache__`-Verzeichnis als Optimierung. Dieser Modulcache, den wir heute verwenden, wurde von PEP 3147 bereitgestellt. Für diesen Vorschlag ist die relevante API für den Modul-Caching das Attribut `__cache__` von Modulen und die Funktion `cache_from_source()` in `importlib.util`. Loader sind dafür verantwortlich, Module in den Cache zu legen (und aus dem Cache zu laden). Derzeit wird der Cache nur für kompilierte Quellmodule verwendet. Loader können jedoch den Modul-Cache für andere Arten von Modulen nutzen.

Paket

Das Konzept ändert sich nicht, und der Begriff auch nicht. Die Unterscheidung zwischen Modulen und Paketen ist jedoch größtenteils oberflächlich. Pakete *sind* Module. Sie verfügen einfach über ein `__path__`-Attribut und Import kann an Untermodule gebundene Attribute hinzufügen. Der typischerweise wahrgenommene Unterschied ist eine Quelle der Verwirrung. Dieser Vorschlag betont die Unterscheidung zwischen Paketen und Modulen explizit dort, wo dies sinnvoll ist.

Motivation

Das Import-System hat sich im Laufe der Lebenszeit von Python entwickelt. Ende 2002 führte PEP 302 standardisierte Import-Hooks über Finder und Loader sowie `sys.meta_path` ein. Das `importlib`-Modul, eingeführt mit Python 3.1, stellt nun eine reine Python-Implementierung der von PEP 302 beschriebenen APIs sowie des vollständigen Import-Systems bereit. Es ist nun viel einfacher, das Import-System zu verstehen und zu erweitern. Während dies ein Vorteil für die Python-Community ist, stellt diese größere Zugänglichkeit auch eine Herausforderung dar.

Da immer mehr Entwickler das Import-System verstehen und anpassen, werden sich Schwächen in den Finder- und Loader-APIs stärker auswirken. Je früher wir also solche Schwächen im Import-System beheben können, desto besser... und es gibt ein paar, die wir hoffen, mit diesem Vorschlag zu beheben.

Erstens, jedes Mal, wenn das Import-System Informationen über ein Modul speichern muss, enden wir mit mehr Attributen für Modulobjekte, die im Allgemeinen nur für das Import-System von Bedeutung sind. Es wäre schön, einen Pro-Modul-Namensraum zu haben, in dem zukünftige Import-bezogene Informationen gespeichert und innerhalb des Import-Systems übergeben werden können. Zweitens gibt es eine API-Lücke zwischen Findern und Loadern, die zu unnötiger Komplexität führt, wenn sie aufgetreten ist. Die Implementierung von PEP 420 (Namespace-Pakete) musste dies umgehen. Die Komplexität trat bei neueren Bemühungen um einen separaten Vorschlag erneut auf. [1]

Die obigen Abschnitte „Finder“ und „Loader“ beschreiben die aktuelle Verantwortung beider. Insbesondere sind Loader nicht verpflichtet, die Funktionalität ihrer `load_module()`-Methode über andere Methoden bereitzustellen. Obwohl die Import-bezogenen Informationen über ein Modul wahrscheinlich ohne Laden des Moduls verfügbar sind, werden sie nicht anderweitig offengelegt.

Darüber hinaus sind die Anforderungen an `load_module()` für alle Loader gleich und werden größtenteils auf exakt dieselbe Weise implementiert. Das bedeutet, dass jeder Loader denselben Boilerplate-Code duplizieren muss. `importlib.util` stellt einige Hilfsmittel zur Verfügung, die dabei helfen, aber es wäre hilfreicher, wenn das Import-System diese Verantwortlichkeiten einfach übernehmen würde. Das Problem ist, dass dies den Grad der Anpassung einschränken würde, den `load_module()` weiterhin problemlos ermöglichen könnte.

Wichtiger ist, dass ein Finder zwar die Informationen bereitstellen *könnte*, die `load_module()` des Loaders benötigen würde, er aber derzeit keine konsistente Möglichkeit hat, diese an den Loader zu übermitteln. Dies ist eine Lücke zwischen Findern und Loadern, die dieser Vorschlag zu schließen beabsichtigt.

Schließlich, wenn das Import-System eine `find_module()`-Methode eines Finders aufruft, nutzt der Finder eine Vielzahl von Informationen über das Modul, die außerhalb des Kontexts der Methode nützlich sind. Derzeit sind die Optionen begrenzt, um diese Pro-Modul-Informationen nach dem Methodenaufruf zu speichern, da nur der Loader zurückgegeben wird. Beliebte Optionen für diese Einschränkung sind, die Informationen in einer Modul-zu-Info-Zuordnung irgendwo auf dem Finder selbst zu speichern oder sie auf dem Loader zu speichern.

Leider sind Loader nicht verpflichtet, modulspezifisch zu sein. Darüber hinaus sind einige der nützlichen Informationen, die Finder bereitstellen könnten, für alle Finder gemeinsam, sodass idealerweise das Import-System diese Details übernehmen könnte. Dies ist dieselbe Lücke wie zuvor zwischen Findern und Loadern.

Als Beispiel für Komplexität, die auf diesen Fehler zurückzuführen ist, fügte die Implementierung von Namespace-Paketen in Python 3.3 (siehe PEP 420) `FileFinder.find_loader()` hinzu, da es keine gute Möglichkeit gab, dass `find_module()` die Namespace-Suchpfade bereitstellt.

Die Antwort auf diese Lücke ist ein `ModuleSpec`-Objekt, das die Pro-Modul-Informationen enthält und die Boilerplate-Funktionalität beim Laden des Moduls übernimmt.

Spezifikation

Ziel ist es, die Lücke zwischen Findern und Loadern zu schließen und dabei so wenig Semantik wie möglich zu ändern. Obwohl einige Funktionalität und Informationen zum neuen `ModuleSpec`-Typ verschoben werden, sollten ihre Verhaltensweisen gleich bleiben. Aus Gründen der Klarheit werden die Semantiken von Finder und Loader jedoch explizit identifiziert.

Hier ist eine High-Level-Zusammenfassung der in dieser PEP beschriebenen Änderungen. Weitere Details finden Sie in späteren Abschnitten.

importlib.machinery.ModuleSpec (neu)

Eine Kapselung des Import-System-bezogenen Zustands eines Moduls während des Imports. Eine detailliertere Beschreibung finden Sie im Abschnitt ModuleSpec unten.

  • ModuleSpec(name, loader, *, origin=None, loader_state=None, is_package=None)

Attribute

  • name - eine Zeichenkette für den vollständig qualifizierten Namen des Moduls.
  • loader - der zu verwendende Loader zum Laden.
  • origin - der Name des Ortes, von dem das Modul geladen wird, z.B. „builtin“ für eingebaute Module und der Dateiname für aus Quelle geladene Module.
  • submodule_search_locations - Liste von Zeichenketten, wo nach Untermodulen gesucht werden soll, falls es sich um ein Paket handelt (andernfalls None).
  • loader_state - ein Container für zusätzliche modulspezifische Daten zur Verwendung beim Laden.
  • cached (Eigenschaft) - eine Zeichenkette, wo das kompilierte Modul gespeichert werden soll.
  • parent (RO-Eigenschaft) - der vollständig qualifizierte Name des Pakets, zu dem das Modul als Untermodul gehört (oder None).
  • has_location (RO-Eigenschaft) - eine Flagge, die angibt, ob das Attribut „origin“ des Moduls einen Ort bezieht.

importlib.util Ergänzungen

Dies sind `ModuleSpec`-Factory-Funktionen, die als Komfort für Finder gedacht sind. Weitere Details finden Sie im Abschnitt Factory-Funktionen unten.

  • spec_from_file_location(name, location, *, loader=None, submodule_search_locations=None) - Erstellt eine Spezifikation aus Datei-orientierten Informationen und Loader-APIs.
  • spec_from_loader(name, loader, *, origin=None, is_package=None) - Erstellt eine Spezifikation mit fehlenden Informationen, die durch die Verwendung von Loader-APIs aufgefüllt werden.

Weitere API-Ergänzungen

  • importlib.find_spec(name, path=None, target=None) funktioniert exakt wie importlib.find_loader() (das es ersetzt), gibt aber eine Spezifikation anstelle eines Loaders zurück.

Für Finder

  • importlib.abc.MetaPathFinder.find_spec(name, path, target) und importlib.abc.PathEntryFinder.find_spec(name, target) geben eine Modulspezifikation zurück, die während des Imports verwendet wird.

Für Loader

  • importlib.abc.Loader.exec_module(module) führt ein Modul in seinem eigenen Namensraum aus. Es ersetzt importlib.abc.Loader.load_module(), indem es die Modulausführungsfunktionalität übernimmt.
  • importlib.abc.Loader.create_module(spec) (optional) gibt das zum Laden zu verwendende Modul zurück.

Für Module

  • Modulobjekte erhalten ein neues Attribut: `__spec__`.

API-Änderungen

  • InspectLoader.is_package() wird optional.

Deprecations

  • importlib.abc.MetaPathFinder.find_module()
  • importlib.abc.PathEntryFinder.find_module()
  • importlib.abc.PathEntryFinder.find_loader()
  • importlib.abc.Loader.load_module()
  • importlib.abc.Loader.module_repr()
  • importlib.util.set_package()
  • importlib.util.set_loader()
  • importlib.find_loader()

Entfernungen

Diese wurden vor der Veröffentlichung von Python 3.4 eingeführt und können daher einfach entfernt werden.

  • importlib.abc.Loader.init_module_attrs()
  • importlib.util.module_to_load()

Andere Änderungen

  • Die Implementierung des Import-Systems in `importlib` wird geändert, um `ModuleSpec` zu nutzen.
  • importlib.reload() wird `ModuleSpec` nutzen.
  • Die Import-bezogenen Attribute eines Moduls (außer `__spec__`) werden während des Imports dieses Moduls nicht mehr direkt vom Import-System verwendet. Dies hat jedoch keine Auswirkungen auf die Verwendung dieser Attribute (z.B. `__path__`) beim Laden anderer Module (z.B. Untermodule).
  • Import-bezogene Attribute sollten nicht mehr direkt zu Modulen hinzugefügt werden, außer durch das Import-System.
  • Das `__repr__()` des Modultyps wird eine dünne Hülle um eine reine Python-Implementierung sein, die auf `ModuleSpec` zugreifen wird.
  • Die Spezifikation für das `__main__`-Modul wird den entsprechenden Namen und Ursprung widerspiegeln.

Abwärtskompatibilität

  • Wenn ein Finder `find_spec()` nicht definiert, wird eine Spezifikation aus dem von `find_module()` zurückgegebenen Loader abgeleitet.
  • `PathEntryFinder.find_loader()` hat weiterhin Priorität vor `find_module()`.
  • `Loader.load_module()` wird verwendet, wenn `exec_module()` nicht definiert ist.

Was wird sich nicht ändern?

  • Die Syntax und Semantik der `import`-Anweisung.
  • Bestehende Finder und Loader funktionieren weiterhin normal.
  • Die Import-bezogenen Modulattribute werden weiterhin mit denselben Informationen initialisiert.
  • Finder erstellen weiterhin Loader (die sie jetzt in Spezifikationen speichern).
  • `Loader.load_module()`, wenn ein Modul es definiert, hat weiterhin alle gleichen Anforderungen und kann direkt aufgerufen werden.
  • Loader sind weiterhin für Moduldaten-APIs zuständig.
  • `importlib.reload()` überschreibt weiterhin die Import-bezogenen Attribute.

Verantwortlichkeiten

Hier ist eine kurze Aufschlüsselung, wer nach dieser PEP für was zuständig ist.

Finder

  • Erstellen/Identifizieren eines Loaders, der das Modul laden kann.
  • Erstellen der Spezifikation für das Modul.

Loader

  • Erstellen des Moduls (optional).
  • Ausführen des Moduls.

ModuleSpec

  • Orchestrierung des Modul-Ladens
  • Boilerplate für das Modul-Laden, einschließlich Verwaltung von `sys.modules` und Festlegen von Import-bezogenen Attributen
  • Erstellen des Moduls, wenn der Loader dies nicht tut
  • Aufruf von `loader.exec_module()`, Übergabe des Moduls, in dem ausgeführt werden soll
  • Enthält alle Informationen, die der Loader zum Ausführen des Moduls benötigt
  • Bereitstellen des `repr` für Module

Was müssen bestehende Finder und Loader anders machen?

Sofort? Nichts. Der Status Quo wird als veraltet markiert, aber weiterhin funktionieren. Hier sind jedoch die Dinge, die die Autoren von Findern und Loadern relativ zu dieser PEP ändern sollten:

  • Implementieren von `find_spec()` auf Findern.
  • Implementieren von `exec_module()` auf Loadern, wenn möglich.

Die `ModuleSpec`-Factory-Funktionen in `importlib.util` sind dazu gedacht, bei der Konvertierung bestehender Finder hilfreich zu sein. `spec_from_loader()` und `spec_from_file_location()` sind in dieser Hinsicht beide einfache Dienstprogramme.

Für bestehende Loader sollte `exec_module()` eine relativ direkte Konvertierung aus dem Nicht-Boilerplate-Teil von `load_module()` sein. In einigen seltenen Fällen sollte der Loader auch `create_module()` implementieren.

ModuleSpec-Benutzer

ModuleSpec-Objekte haben 3 verschiedene Zielgruppen: Python selbst, Import-Hooks und normale Python-Benutzer.

Python wird Spezifikationen in der Import-Maschinerie, beim Interpreterstart und in verschiedenen Standardbibliotheksmodulen verwenden. Einige Module sind Import-orientiert, wie `pkgutil`, andere nicht, wie `pickle` und `pydoc`. In allen Fällen wird die vollständige `ModuleSpec`-API genutzt.

Import-Hooks (Finder und Loader) werden die Spezifikation auf spezifische Weise nutzen. Erstens können Finder die Spezifikations-Factory-Funktionen in `importlib.util` verwenden, um Spezifikationsobjekte zu erstellen. Sie können auch die Spezifikationsattribute direkt nach der Erstellung der Spezifikation anpassen. Zweitens kann der Finder zusätzliche Informationen an die Spezifikation binden (in `finder_extras`) für den Loader zur Verbrauch während der Modulerstellung/Ausführung. Schließlich werden Loader die Attribute einer Spezifikation beim Erstellen und/oder Ausführen eines Moduls nutzen.

Python-Benutzer können das `__spec__` eines Moduls inspizieren, um Import-bezogene Informationen über das Objekt zu erhalten. Im Allgemeinen werden Python-Anwendungen und interaktive Benutzer nicht die `ModuleSpec`-Factory-Funktionen oder deren Instanzmethoden verwenden.

Wie das Laden funktionieren wird

Hier ist eine Übersicht darüber, was die Import-Maschinerie während des Ladens tut, angepasst an die Nutzung der Modulspezifikation und der neuen Loader-API.

module = None
if spec.loader is not None and hasattr(spec.loader, 'create_module'):
    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 and spec.submodule_search_locations is not None:
    # Namespace package
    sys.modules[spec.name] = module
elif not hasattr(spec.loader, 'exec_module'):
    spec.loader.load_module(spec.name)
    # __loader__ and __package__ would be explicitly set here for
    # backwards-compatibility.
else:
    sys.modules[spec.name] = module
    try:
        spec.loader.exec_module(module)
    except BaseException:
        try:
            del sys.modules[spec.name]
        except KeyError:
            pass
        raise
module_to_return = sys.modules[spec.name]

Diese Schritte sind genau das, was `Loader.load_module()` bereits tun soll. Loader werden somit vereinfacht, da sie nur `exec_module()` implementieren müssen.

Beachten Sie, dass wir das Modul aus `sys.modules` zurückgeben müssen. Während des Ladens hat das Modul sich möglicherweise selbst in `sys.modules` ersetzt. Da wir keine Post-Import-Hook-API haben, um diesen Anwendungsfall zu berücksichtigen, müssen wir damit umgehen. Im Falle eines Ersetzens kümmern wir uns jedoch nicht um das Festlegen der Import-bezogenen Modulattribute für das Objekt. Der Modulautor ist auf sich allein gestellt, wenn er dies tut.

Wie das Neuladen funktionieren wird

Hier ist die entsprechende Übersicht für `reload()`.

_RELOADING = {}

def reload(module):
    try:
        name = module.__spec__.name
    except AttributeError:
        name = module.__name__
    spec = find_spec(name, target=module)

    if sys.modules.get(name) is not module:
        raise ImportError
    if spec in _RELOADING:
        return _RELOADING[name]
    _RELOADING[name] = module
    try:
        if spec.loader is None:
            # Namespace loader
            _init_module_attrs(spec, module)
            return module
        if spec.parent and spec.parent not in sys.modules:
            raise ImportError

        _init_module_attrs(spec, module)
        # Ignoring backwards-compatibility call to load_module()
        # for simplicity.
        spec.loader.exec_module(module)
        return sys.modules[name]
    finally:
        del _RELOADING[name]

Ein wichtiger Punkt hier ist der Wechsel zu `Loader.exec_module()`, was bedeutet, dass Loader keine einfache Möglichkeit mehr haben, zur Ausführungszeit zu erkennen, ob es sich um einen Neuladevorgang handelt oder nicht. Vor diesem Vorschlag konnten sie einfach prüfen, ob das Modul bereits in `sys.modules` vorhanden war. Jetzt, wenn `exec_module()` während des Ladens (nicht des Neuladens) aufgerufen wird, hätte die Import-Maschinerie das Modul bereits in `sys.modules` platziert. Dies ist einer der Gründe, warum `find_spec()` über den „target“-Parameter verfügt.

Die Semantik von `reload()` bleibt im Wesentlichen dieselbe wie bisher [5]. Die Auswirkungen dieser PEP auf bestimmte Arten von Lazy-Loading-Modulen waren ein Diskussionspunkt. [4]

ModuleSpec

Attribute

Jeder der folgenden Namen ist ein Attribut von `ModuleSpec`-Objekten. Ein Wert von `None` bedeutet „nicht gesetzt“. Dies steht im Gegensatz zu Modulobjekten, bei denen das Attribut einfach nicht existiert. Die meisten Attribute entsprechen den Import-bezogenen Attributen von Modulen. Hier ist die Zuordnung. Die Umkehrung dieser Zuordnung beschreibt, wie die Import-Maschinerie die Modulattribute kurz vor dem Aufruf von `exec_module()` setzt.

Auf ModuleSpec Auf Modulen
name __name__
Loader __loader__
parent __package__
Ursprung __file__*
cached __cached__*,**
submodule_search_locations __path__**
loader_state -
has_location -
* Nur gesetzt, wenn `spec.has_location` wahr ist.
** Nur gesetzt, wenn das Spezifikationsattribut nicht `None` ist.

Während `parent` und `has_location` schreibgeschützte Eigenschaften sind, können die übrigen Attribute nach der Erstellung der Modulspezifikation und sogar nach Abschluss des Imports ersetzt werden. Dies ermöglicht ungewöhnliche Fälle, in denen die direkte Änderung der Spezifikation die beste Option ist. Die typische Verwendung sollte jedoch nicht die Änderung des Zustands einer Modulspezifikation beinhalten.

Ursprung

„origin“ ist eine Zeichenkette für den Namen des Ortes, von dem das Modul stammt. Siehe „Ursprung“ oben. Neben dem informativen Wert wird es auch im `repr` des Moduls verwendet. Im Falle einer Spezifikation, bei der „has_location“ wahr ist, wird `__file__` auf den Wert von „origin“ gesetzt. Für eingebaute Module wird „origin“ auf „built-in“ gesetzt.

has_location

Wie im Abschnitt „Ort“ oben erläutert, sind viele Module „lokalisierbar“, d. h. es gibt eine entsprechende Ressource, aus der das Modul geladen wird, und diese Ressource kann durch eine Zeichenkette beschrieben werden. Im Gegensatz dazu können nicht lokalisierbare Module nicht auf diese Weise geladen werden, z.B. eingebaute Module und Module, die dynamisch im Code erstellt werden. Für diese ist der Name die einzige Möglichkeit, auf sie zuzugreifen, sodass sie einen „Ursprung“, aber keinen „Ort“ haben.

„has_location“ ist wahr, wenn das Modul lokalisierbar ist. In diesem Fall wird der Ursprung der Spezifikation als Ort verwendet und `__file__` auf `spec.origin` gesetzt. Wenn zusätzliche Ortsinformationen erforderlich sind (z.B. zipimport), können diese in `spec.loader_state` gespeichert werden.

„has_location“ kann aus der Existenz einer `load_data()`-Methode im Loader abgeleitet werden.

Nebenbei bemerkt, nicht alle lokalisierbaren Module werden cachebar sein, aber die meisten werden es sein.

submodule_search_locations

Die Liste der Ortszeichenketten, typischerweise Verzeichnispfade, in denen nach Untermodulen gesucht werden soll. Wenn das Modul ein Paket ist, wird dies auf eine Liste gesetzt (auch eine leere). Andernfalls ist es `None`.

Der Name des entsprechenden Modulattributs, `__path__`, ist relativ mehrdeutig. Anstatt ihn zu spiegeln, verwenden wir einen expliziteren Attributnamen, der den Zweck verdeutlicht.

loader_state

Ein Finder kann `loader_state` auf jeden Wert setzen, um zusätzliche Daten für den Loader bereitzustellen, die während des Ladens verwendet werden. Ein Wert von `None` ist der Standard und bedeutet, dass keine zusätzlichen Daten vorhanden sind. Andernfalls kann er auf jedes Objekt gesetzt werden, wie z.B. ein `dict`, eine `list` oder `types.SimpleNamespace`, das die relevanten zusätzlichen Informationen enthält.

Zum Beispiel könnte `zipimporter` ihn verwenden, um den Namen des Zip-Archivs direkt an den Loader zu übergeben, anstatt ihn vom Ursprung ableiten zu müssen oder für jede Suchoperation einen benutzerdefinierten Loader zu erstellen.

`loader_state` ist für die Verwendung durch den Finder und den entsprechenden Loader gedacht. Es ist keine garantierte stabile Ressource für andere Zwecke.

Factory-Funktionen

spec_from_file_location(name, location, *, loader=None, submodule_search_locations=None)

Erstellt eine Spezifikation aus Datei-orientierten Informationen und Loader-APIs.

  • „origin“ wird auf den Ort gesetzt.
  • „has_location“ wird auf `True` gesetzt.
  • „cached“ wird auf das Ergebnis des Aufrufs von `cache_from_source()` gesetzt.
  • „origin“ kann aus `loader.get_filename()` abgeleitet werden (wenn „location“ nicht übergeben wird).
  • „loader“ kann aus der Erweiterung abgeleitet werden, wenn der Ort ein Dateiname ist.
  • „submodule_search_locations“ kann aus `loader.is_package()` und aus `os.path.dirname(location)` abgeleitet werden, wenn der Ort ein Dateiname ist.

spec_from_loader(name, loader, *, origin=None, is_package=None)

Erstellt eine Spezifikation mit fehlenden Informationen, die durch die Verwendung von Loader-APIs aufgefüllt werden.

  • „has_location“ kann aus `loader.get_data` abgeleitet werden.
  • „origin“ kann aus `loader.get_filename()` abgeleitet werden.
  • „submodule_search_locations“ kann aus `loader.is_package()` und aus `os.path.dirname(location)` abgeleitet werden, wenn der Ort ein Dateiname ist.

Abwärtskompatibilität

ModuleSpec hat keine. Dies wäre eine andere Geschichte, wenn `Finder.find_module()` eine Modulspezifikation anstelle eines Loaders zurückgeben würde. In diesem Fall müssten Spezifikationen wie der zurückgegebene Loader fungieren. Dies wäre relativ einfach, ist aber eine unnötige Komplikation. Es war Teil früherer Versionen dieser PEP.

Unterklasse

Unterklassen von `ModuleSpec` sind erlaubt, sollten aber nicht notwendig sein. Einfaches Setzen von `loader_state` oder Hinzufügen von Funktionalität zu einem benutzerdefinierten Finder oder Loader wird wahrscheinlich besser passen und sollte zuerst versucht werden. Solange eine Unterklasse jedoch die Anforderungen des Import-Systems erfüllt, sind Objekte dieses Typs als Rückgabewert von `Finder.find_spec()` völlig in Ordnung. Die gleichen Punkte gelten für Duck-Typing.

Bestehende Typen

Modulobjekte

Abgesehen von der Hinzufügung von `__spec__` werden keine der Import-bezogenen Modulattribute geändert oder als veraltet markiert, obwohl einige davon es sein könnten; jede solche Veralterung kann bis Python 4 warten.

Die Spezifikation eines Moduls wird nicht mit den entsprechenden Import-bezogenen Attributen synchron gehalten. Obwohl sie unterschiedlich sein können, werden sie in der Praxis typischerweise gleich sein.

Eine bemerkenswerte Ausnahme ist der Fall, in dem ein Modul als Skript mit dem `-m`-Flag ausgeführt wird. In diesem Fall spiegelt `module.__spec__.name` den tatsächlichen Modulnamen wider, während `module.__name__` `__main__` sein wird.

Die Spezifikation eines Moduls ist nicht garantiert identisch zwischen zwei Modulen mit demselben Namen. Ebenso gibt es keine Garantie, dass aufeinanderfolgende Aufrufe von `importlib.find_spec()` dasselbe Objekt oder sogar ein äquivalentes Objekt zurückgeben, obwohl zumindest Letzteres wahrscheinlich ist.

Finder

Finder sind weiterhin dafür verantwortlich, den Loader zu identifizieren und typischerweise zu erstellen, der zum Laden eines Moduls verwendet werden soll. Dieser Loader wird nun in der von `find_spec()` zurückgegebenen Modulspezifikation gespeichert, anstatt direkt zurückgegeben zu werden. Wie derzeit ohne die PEP, wenn die Erstellung eines Loaders kostspielig wäre, kann dieser Loader so konzipiert werden, dass die Kosten bis später aufgeschoben werden.

MetaPathFinder.find_spec(name, path=None, target=None)

PathEntryFinder.find_spec(name, target=None)

Finder müssen `ModuleSpec`-Objekte zurückgeben, wenn `find_spec()` aufgerufen wird. Diese neue Methode ersetzt `find_module()` und `find_loader()` (im Falle von `PathEntryFinder`). Wenn ein Loader keine `find_spec()` hat, werden zur Abwärtskompatibilität stattdessen `find_module()` und `find_loader()` verwendet.

Das Hinzufügen einer weiteren ähnlichen Methode zu Loadern ist eine Frage der Praktikabilität. `find_module()` könnte so geändert werden, dass es Spezifikationen anstelle von Loadern zurückgibt. Dies ist verlockend, da die Import-APIs genug gelitten haben, insbesondere da `PathEntryFinder.find_loader()` gerade erst in Python 3.3 hinzugefügt wurde. Die zusätzliche Komplexität und ein nicht ganz eindeutiger Methodenname sind es jedoch nicht wert.

Der Parameter „target“ von find_spec()

Ein Aufruf von `find_spec()` kann optional ein „target“-Argument enthalten. Dies ist das Modulobjekt, das anschließend als Ziel des Ladens verwendet wird. Während des normalen Imports (und standardmäßig) ist „target“ `None`, was bedeutet, dass das Zielmodul noch nicht erstellt wurde. Während des Neuladens wird das an `reload()` übergebene Modul als Ziel an `find_spec()` übergeben. Dieses Argument ermöglicht es dem Finder, die Modulspezifikation mit mehr Informationen zu erstellen, als sonst verfügbar sind. Dies ist besonders relevant bei der Identifizierung des zu verwendenden Loaders.

Über `find_spec()` identifiziert der Finder immer den Loader, den er in der Spezifikation zurückgeben wird (oder gibt `None` zurück). An dem Punkt, an dem der Loader identifiziert ist, sollte der Finder auch entscheiden, ob der Loader das Laden in das Zielmodul unterstützt, falls „target“ übergeben wird. Diese Entscheidung kann die Konsultation des Loaders beinhalten.

Wenn der Finder feststellt, dass der Loader das Laden in das Zielmodul nicht unterstützt, sollte er entweder einen anderen Loader finden oder `ImportError` auslösen (was den Import des Moduls vollständig stoppt). Diese Bestimmung ist besonders wichtig während des Neuladens, da, wie in „Wie das Neuladen funktionieren wird“ erwähnt, Loader eine Neuladesituation nicht mehr trivial selbst identifizieren können.

Zwei Alternativen wurden zum „target“-Parameter präsentiert: `Loader.supports_reload()` und das Hinzufügen von „target“ zu `Loader.exec_module()` anstelle von `find_spec()`. `supports_reload()` war der ursprüngliche Ansatz für die Neuladesituation. [6] Es gab jedoch einige Einwände gegen den Loader-spezifischen, neuladezentrierten Ansatz. [7]

Was „target“ auf `exec_module()` betrifft, so benötigt der Loader möglicherweise weitere Informationen vom Zielmodul (oder der Spezifikation) während des Neuladens, mehr als nur „unterstützt dieser Loader das Neuladen dieses Moduls“, das mit der Abkehr von `load_module()` nicht mehr verfügbar ist. Ein Vorschlag war, etwas wie „target“ zu `exec_module()` hinzuzufügen. [8] Das Platzieren von „target“ in `find_spec()` stattdessen entspricht jedoch besser den Zielen dieser PEP. Darüber hinaus entfällt die Notwendigkeit von `supports_reload()`.

Namespace-Pakete

Derzeit kann ein Pfadeintragsfinder `(None, portions)` von `find_loader()` zurückgeben, um anzuzeigen, dass er einen Teil eines möglichen Namespace-Pakets gefunden hat. Um denselben Effekt zu erzielen, muss `find_spec()` eine Spezifikation mit „loader“ auf `None` (auch bekannt als nicht gesetzt) und mit `submodule_search_locations` auf dieselben Teile gesetzt zurückgeben, die von `find_loader()` bereitgestellt worden wären. Es liegt an `PathFinder`, solche Spezifikationen zu handhaben.

Loader

Loader.exec_module(module)

Loader werden eine neue Methode, `exec_module()`, haben. Ihre einzige Aufgabe ist es, das Modul auszuführen und folglich den Namensraum des Moduls zu füllen. Sie ist nicht für die Erstellung oder Vorbereitung des Modulobjekts zuständig, noch für Aufräumarbeiten danach. Sie gibt nichts zurück. `exec_module()` wird sowohl beim Laden als auch beim Neuladen verwendet.

`exec_module()` sollte den Fall, dass es mehr als einmal aufgerufen wird, ordnungsgemäß behandeln. Für einige Arten von Modulen kann dies bedeuten, jedes Mal nach dem ersten Aufruf der Methode `ImportError` auszulösen. Dies ist besonders relevant für das Neuladen, da einige Arten von Modulen kein In-Place-Neuladen unterstützen.

Loader.create_module(spec)

Loader können auch `create_module()` implementieren, das ein neues Modul zum Ausführen zurückgibt. Es kann `None` zurückgeben, um anzuzeigen, dass der Standard-Modulerstellungscode verwendet werden soll. Ein Anwendungsfall, wenn auch untypisch, für `create_module()` ist die Bereitstellung eines Moduls, das eine Unterklasse des integrierten Modultyps ist. Die meisten Loader werden `create_module()` nicht implementieren müssen.

`create_module()` sollte den Fall, dass es mehr als einmal für dieselbe Spezifikation/dasselbe Modul aufgerufen wird, ordnungsgemäß behandeln. Dies kann das Zurückgeben von `None` oder das Auslösen von `ImportError` beinhalten.

Hinweis

`exec_module()` und `create_module()` sollten keine Import-bezogenen Modulattribute setzen. Die Tatsache, dass `load_module()` dies tut, ist ein Designfehler, den dieser Vorschlag zu korrigieren versucht.

Weitere Änderungen

PEP 420 führte die optionale `module_repr()` Loader-Methode ein, um die Menge an Sonderbehandlung im `__repr__()` des Modultyps zu begrenzen. Da diese Methode Teil von `ModuleSpec` ist, wird sie auf Loadern als veraltet markiert. Wenn sie jedoch auf einem Loader existiert, wird sie exklusiv verwendet.

Die `Loader.init_module_attr()`-Methode, die vor der Veröffentlichung von Python 3.4 hinzugefügt wurde, wird zugunsten derselben Methode auf `ModuleSpec` entfernt.

Jedoch wird InspectLoader.is_package() nicht als veraltet markiert, obwohl dieselben Informationen auf ModuleSpec zu finden sind. ModuleSpec kann diese verwenden, um sein eigenes is_package zu befüllen, falls diese Information anderweitig nicht verfügbar ist. Dennoch wird es optional gemacht.

Zusätzlich zur Ausführung eines Moduls während des Ladens werden Loader weiterhin direkt für die Bereitstellung von APIs bezüglich modulspezifischer Daten verantwortlich sein.

Andere Änderungen

  • Die verschiedenen von importlib bereitgestellten Finder und Loader werden aktualisiert, um dieser Vorschlag zu entsprechen.
  • Jegliche andere Implementierungen von oder Abhängigkeiten von den Import-bezogenen APIs (insbesondere Finder und Loader) in der Standardbibliothek werden ebenfalls an diese PEP angepasst. Obwohl sie weiterhin funktionieren sollten, sollten solche Änderungen, die übersehen werden, als Fehler für die Python 3.4.x-Serie betrachtet werden.
  • Die Spezifikation für das Modul __main__ wird widerspiegeln, wie der Interpreter gestartet wurde. Zum Beispiel wird bei -m der Name der Spezifikation der verwendete Modulname sein, während __main__.__name__ weiterhin „__main__“ sein wird.
  • Wir werden importlib.find_spec() hinzufügen, um importlib.find_loader() zu spiegeln (welches veraltet wird).
  • importlib.reload() wird geändert, um ModuleSpec zu verwenden.
  • importlib.reload() wird nun die pro-Modul-Import-Sperre nutzen.

Referenzimplementierung

Eine Referenzimplementierung ist verfügbar unter http://bugs.python.org/issue18864.

Implementierungs-Hinweise

* Die Implementierung dieser PEP muss sich der Auswirkungen auf pkgutil (und setuptools) bewusst sein. pkgutil hat einige generische funktionsbasierte Erweiterungen für PEP 302, die fehlschlagen könnten, wenn importlib Loader ohne Wissen des Tools umschließt.

* Weitere zu betrachtende Module: runpy (und pythonrun.c), pickle, pydoc, inspect.

Zum Beispiel sollte pickle im __main__-Fall aktualisiert werden, um auf module.__spec__.name zuzugreifen.

Abgelehnte Ergänzungen zur PEP

Es gab einige vorgeschlagene Ergänzungen zu diesem Vorschlag, die nicht gut in seinen Rahmen passten.

Es gibt keine „PathModuleSpec“-Unterklasse von ModuleSpec, die has_location, cached und submodule_search_locations trennt. Während dies die Trennung sauberer machen könnte, haben Modulobjekte diese Unterscheidung nicht. ModuleSpec wird beide Fälle gleichermaßen unterstützen.

Während „ModuleSpec.is_package“ ein einfaches zusätzliches Attribut wäre (ein Alias für self.submodule_search_locations is not None), verewigt es die künstliche (und meist fehlerhafte) Unterscheidung zwischen Modulen und Paketen.

Die Modulspezifikations- Factory Functions könnten Classmethods auf ModuleSpec sein. Dies würde sie jedoch auf *allen* Modulen über __spec__ verfügbar machen, was das Potenzial hat, fortgeschrittene Python-Benutzer unnötig zu verwirren. Die Factory Functions haben einen spezifischen Anwendungsfall zur Unterstützung von Finder-Autoren. Siehe ModuleSpec Users.

Ebenso könnten mehrere andere Methoden zu ModuleSpec hinzugefügt werden, die die spezifischen Verwendungen von Modulspezifikationen durch die Import-Maschinerie offenlegen

  • create() - ein Wrapper um Loader.create_module().
  • exec(module) - ein Wrapper um Loader.exec_module().
  • load() - ein Analogon zum veralteten Loader.load_module().

Wie bei den Factory Functions ist es weniger wünschenswert, diese Methoden über module.__spec__ verfügbar zu machen. Sie würden zu einer attraktiven Ablenkung werden, selbst wenn sie nur als „private“ Attribute (wie in früheren Versionen dieser PEP) verfügbar wären. Wenn jemand später einen Bedarf für diese Methoden findet, können wir sie zu gegebener Zeit über eine geeignete API (getrennt von ModuleSpec) verfügbar machen, vielleicht im Zusammenhang mit PEP 406 (Import-Engine).

Denkbar wäre, dass die load()-Methode optional eine Liste von Modulen entgegennehmen könnte, mit denen sie interagieren soll, anstatt sys.modules. Außerdem könnte load() zur Implementierung von Multi-Version-Importen genutzt werden. Beides sind interessante Ideen, aber definitiv außerhalb des Geltungsbereichs dieses Vorschlags.

Andere ausgeschlossen

  • Add ModuleSpec.submodules (RO-property) - gibt mögliche Untermodule relativ zur Spezifikation zurück.
  • Add ModuleSpec.loaded (RO-property) - das Modul in sys.module, falls vorhanden.
  • Add ModuleSpec.data - ein Deskriptor, der die Daten-API des Loaders der Spezifikation umschließt.
  • Siehe auch [3].

Referenzen


Source: https://github.com/python/peps/blob/main/peps/pep-0451.rst

Zuletzt geändert: 2025-02-01 08:59:27 GMT