PEP 302 – Neue Import-Hooks
- Autor:
- Just van Rossum <just at letterror.com>, Paul Moore <p.f.moore at gmail.com>
- Status:
- Final
- Typ:
- Standards Track
- Erstellt:
- 19. Dez. 2002
- Python-Version:
- 2.3
- Post-History:
- 19. Dez. 2002
Inhaltsverzeichnis
- Zusammenfassung
- Motivation
- Anwendungsfälle
- Begründung
- Spezifikation Teil 1: Das Importer-Protokoll
- Spezifikation Teil 2: Registrieren von Hooks
- Pakete und die Rolle von
__path__ - Optionale Erweiterungen des Importer-Protokolls
- Integration in das „imp“-Modul
- Vorwärtskompatibilität
- Offene Fragen
- Implementierung
- Referenzen und Fußnoten
- Urheberrecht
Warnung
Die Sprachreferenz für Import [10] und die Dokumentation von importlib [11] ersetzen nun diese PEP. Dieses Dokument wird nicht mehr aktualisiert und dient nur zu historischen Zwecken.
Zusammenfassung
Diese PEP schlägt eine neue Reihe von Import-Hooks vor, die eine bessere Anpassung des Python-Importmechanismus ermöglichen. Im Gegensatz zum aktuellen __import__-Hook kann ein Hook des neuen Stils in das bestehende Schema eingefügt werden, was eine feinere Steuerung der Modulsuche und -ladung ermöglicht.
Motivation
Die einzige Möglichkeit, den Importmechanismus anzupassen, besteht derzeit darin, die eingebaute Funktion __import__ zu überschreiben. Das Überschreiben von __import__ hat jedoch viele Probleme. Zunächst einmal:
- Ein
__import__-Ersatz muss den gesamten Importmechanismus *vollständig* neu implementieren oder die ursprüngliche__import__-Funktion vor oder nach dem benutzerdefinierten Code aufrufen. - Er hat sehr komplexe Semantik und Verantwortlichkeiten.
__import__wird auch für Module aufgerufen, die sich bereits insys.modulesbefinden, was fast nie gewünscht ist, es sei denn, man schreibt eine Art Überwachungswerkzeug.
Die Situation verschlechtert sich, wenn der Importmechanismus aus C erweitert werden muss: Dies ist derzeit unmöglich, abgesehen davon, dass Pythons import.c gehackt wird oder viel von import.c von Grund auf neu implementiert wird.
Es gibt eine ziemlich lange Geschichte von in Python geschriebenen Werkzeugen, die die Erweiterung des Importmechanismus auf verschiedene Weise auf Basis des __import__-Hooks ermöglichen. Die Standardbibliothek enthält zwei solche Werkzeuge: ihooks.py (von GvR) und imputil.py [1] (Greg Stein), aber vielleicht das berühmteste ist iu.py von Gordon McMillan, das als Teil seines Installer-Pakets erhältlich ist. Ihre Nützlichkeit ist etwas eingeschränkt, da sie in Python geschrieben sind; Bootstrap-Probleme müssen umgangen werden, da das Modul, das den Hook enthält, nicht mit dem Hook selbst geladen werden kann. Wenn also die gesamte Standardbibliothek aus einem Import-Hook geladen werden soll, muss der Hook in C geschrieben sein.
Anwendungsfälle
Dieser Abschnitt listet mehrere bestehende Anwendungen auf, die von Import-Hooks abhängen. Unter diesen wurde viel doppelte Arbeit geleistet, die hätte vermieden werden können, wenn damals ein flexiblerer Import-Hook zur Verfügung gestanden hätte. Diese PEP sollte ähnlichen Projekten in Zukunft das Leben erheblich erleichtern.
Die Erweiterung des Importmechanismus ist erforderlich, wenn Module geladen werden sollen, die auf eine nicht standardmäßige Weise gespeichert sind. Beispiele hierfür sind Module, die in einem Archiv gebündelt sind; Bytecode, der nicht in einer Datei im pyc-Format gespeichert ist; Module, die von einer Datenbank über ein Netzwerk geladen werden.
Die Arbeit an dieser PEP wurde teilweise durch die Implementierung von PEP 273 ausgelöst, die den Import aus Zip-Archiven als integrierte Funktion in Python hinzufügt. Obwohl die PEP selbst weithin als unverzichtbare Funktion akzeptiert wurde, ließ die Implementierung einiges zu wünschen übrig. Einerseits wurde viel Aufwand betrieben, um sich in import.c zu integrieren, und viel Code hinzugefügt, der entweder spezifisch für Zip-Datei-Importe oder *nicht* spezifisch für Zip-Importe war, aber dennoch nicht allgemein nützlich (oder sogar wünschenswert) war. Dennoch kann der Implementierung von PEP 273 dies kaum zum Vorwurf gemacht werden: Es ist angesichts des aktuellen Zustands von import.c einfach extrem schwierig.
Das Verpacken von Anwendungen für Endbenutzer ist ein typischer Anwendungsfall für Import-Hooks, wenn nicht sogar *der* typische Anwendungsfall. Die Verbreitung vieler Quell- oder pyc-Dateien ist nicht immer angebracht (ganz zu schweigen von einer separaten Python-Installation), sodass der Wunsch, alle benötigten Module in einer einzigen Datei zu verpacken, häufig besteht. So häufig sogar, dass im Laufe der Jahre mehrere Lösungen implementiert wurden.
Die älteste ist im Python-Quellcode enthalten: Freeze [2]. Sie packt marshallierten Bytecode in statische Objekte im C-Quellcode. Der „Import-Hook“ von Freeze ist fest in import.c verdrahtet und hat einige Probleme. Spätere Lösungen sind Fredrik Lundhs Squeeze, Gordon McMillans Installer und Thomas Hellers py2exe [3]. MacPython wird mit einem Werkzeug namens BuildApplication ausgeliefert.
Squeeze, Installer und py2exe verwenden ein __import__-basiertes Schema (py2exe verwendet derzeit Installers iu.py, Squeeze verwendete ihooks.py), MacPython hat zwei Mac-spezifische Import-Hooks, die fest in import.c verdrahtet sind und dem Freeze-Hook ähneln. Die in dieser PEP vorgeschlagenen Hooks ermöglichen es uns (zumindest theoretisch; dies ist kein kurzfristiges Ziel), die fest codierten Hooks in import.c zu entfernen und würden es den __import__-basierten Werkzeugen ermöglichen, den Großteil ihres import.c-Emulationscodes zu entfernen.
Bevor mit der Ausarbeitung des Designs und der Implementierung dieser PEP begonnen wurde, veranlasste ein neues Werkzeug im Stil von BuildApplication für Mac OS X einen der Autoren dieser PEP (JvR), die Tabelle der eingefrorenen Module für Python im Modul imp zugänglich zu machen. Der Hauptgrund dafür war, den Freeze-Import-Hook verwenden zu können (ohne aufwendige __import__-Unterstützung), aber auch, um zur Laufzeit einen Satz von Modulen bereitstellen zu können. Dies führte zu Issue #642578 [4], das auf mysteriöse Weise akzeptiert wurde (hauptsächlich, weil es niemanden zu interessieren schien ;-). Es ist jedoch völlig überflüssig, wenn diese PEP akzeptiert wird, da sie eine viel schönere und allgemeinere Möglichkeit bietet, dasselbe zu tun.
Begründung
Bei Experimenten mit alternativen Implementierungsideen für den integrierten Zip-Import wurde festgestellt, dass dies mit nur geringfügigen Änderungen an import.c möglich ist. Dies ermöglichte es, die Zip-spezifischen Teile in eine neue Quelldatei auszulagern und gleichzeitig ein *allgemeines* neues Import-Hook-Schema zu schaffen: das, das Sie hier lesen.
Ein früheres Design erlaubte Nicht-String-Objekte auf sys.path. Ein solches Objekt hätte die notwendigen Methoden, um einen Import zu verarbeiten. Dies hat zwei Nachteile: 1) Es bricht Code, der davon ausgeht, dass alle Elemente in sys.path Zeichenketten sind; 2) Es ist nicht kompatibel mit der Umgebungsvariablen PYTHONPATH. Letzteres ist für Zip-Importe direkt erforderlich. Ein Kompromiss ergab sich aus Jython: Erlaube String-*Unterklassen* auf sys.path, die dann als Importer-Objekte fungieren würden. Dies vermeidet einige Brüche und scheint für Jython (wo es zum Laden von Modulen aus .jar-Dateien verwendet wird) gut zu funktionieren, wurde aber als „hässlicher Hack“ empfunden.
Dies führte zu einem ausgefeilteren Schema (hauptsächlich kopiert von McMillans iu.py), bei dem jede in einer Liste von Kandidaten gefragt wird, ob sie das sys.path-Element verarbeiten kann, bis einer gefunden wird, der es kann. Diese Liste von Kandidaten ist ein neues Objekt im sys-Modul: sys.path_hooks.
Das Durchlaufen von sys.path_hooks für jedes Pfadelement bei jedem neuen Import kann teuer sein, daher werden die Ergebnisse in einem weiteren neuen Objekt im sys-Modul zwischengespeichert: sys.path_importer_cache. Es ordnet sys.path-Einträge Importer-Objekten zu.
Um die Auswirkungen auf import.c zu minimieren und zusätzlichen Overhead zu vermeiden, wurde beschlossen, keinen expliziten Hook und kein Importer-Objekt für die bestehende Dateisystem-Importlogik hinzuzufügen (wie es iu.py tut), sondern einfach auf die eingebaute Logik zurückzufallen, wenn kein Hook auf sys.path_hooks das Pfadelement verarbeiten konnte. In diesem Fall wird ein None-Wert in sys.path_importer_cache gespeichert, um wiederholte Suchen zu vermeiden. (Später können wir weiter gehen und ein echtes Importer-Objekt für den eingebauten Mechanismus hinzufügen, vorerst sollte das None-Fallback-Schema ausreichen.)
Eine Frage wurde aufgeworfen: Was ist mit Importern, die *keinen* Eintrag auf sys.path benötigen? (Eingebaute und eingefrorene Module fallen in diese Kategorie.) Wieder hilft Gordon McMillan: iu.py enthält etwas, das er den *Metapfad* nennt. In der Implementierung dieser PEP ist es eine Liste von Importer-Objekten, die *vor* sys.path durchlaufen wird. Diese Liste ist ein weiteres neues Objekt im sys-Modul: sys.meta_path. Derzeit ist diese Liste standardmäßig leer, und der Import von eingefrorenen und eingebauten Modulen erfolgt nach dem Durchlaufen von sys.meta_path, aber immer noch vor sys.path.
Spezifikation Teil 1: Das Importer-Protokoll
Diese PEP führt ein neues Protokoll ein: das „Importer-Protokoll“. Es ist wichtig, den Kontext zu verstehen, in dem das Protokoll operiert, daher hier ein kurzer Überblick über die äußeren Hüllen des Importmechanismus.
Wenn eine Importanweisung angetroffen wird, sucht der Interpreter die Funktion __import__ im integrierten Namensraum. __import__ wird dann mit vier Argumenten aufgerufen, unter denen sich der Name des zu importierenden Moduls (kann ein Punkt-Name sein) und ein Verweis auf den aktuellen globalen Namensraum befinden.
Die eingebaute Funktion __import__ (bekannt als PyImport_ImportModuleEx() in import.c) prüft dann, ob das importierende Modul ein Paket oder ein Untermodul eines Pakets ist. Wenn es sich tatsächlich um ein (Untermodul eines) Pakets handelt, versucht es zunächst, den Import relativ zum Paket durchzuführen (das Elternpaket für ein Untermodul). Wenn zum Beispiel ein Paket namens „spam“ „import eggs“ durchführt, sucht es zuerst nach einem Modul namens „spam.eggs“. Wenn dies fehlschlägt, wird der Import als absoluter Import fortgesetzt: Es wird nach einem Modul namens „eggs“ gesucht. Punktierte Namensimporte funktionieren ziemlich ähnlich: Wenn das Paket „spam“ „import eggs.bacon“ durchführt (und „spam.eggs“ existiert und selbst ein Paket ist), wird „spam.eggs.bacon“ versucht. Wenn dies fehlschlägt, wird „eggs.bacon“ versucht. (Es gibt weitere Feinheiten, die hier nicht beschrieben sind, aber diese sind für Implementierer des Importer-Protokolls nicht relevant.)
Tiefer im Mechanismus wird ein Punkt-Name durch seine Komponenten aufgeteilt. Bei „import spam.ham“ wird zunächst „import spam“ durchgeführt, und erst wenn dies erfolgreich ist, wird „ham“ als Untermodul von „spam“ importiert.
Das Importer-Protokoll operiert auf dieser Ebene von *einzelnen* Importen. Zu dem Zeitpunkt, an dem ein Importer eine Anfrage für „spam.ham“ erhält, wurde das Modul „spam“ bereits importiert.
Das Protokoll beinhaltet zwei Objekte: einen *Finder* und einen *Loader*. Ein Finder-Objekt hat eine einzige Methode
finder.find_module(fullname, path=None)
Diese Methode wird mit dem vollständig qualifizierten Namen des Moduls aufgerufen. Wenn der Finder auf sys.meta_path installiert ist, erhält er ein zweites Argument, das für ein Top-Level-Modul None ist oder package.__path__ für Untermodule oder Unterpakete [5]. Es sollte ein Loader-Objekt zurückgeben, wenn das Modul gefunden wurde, oder None, wenn es nicht gefunden wurde. Wenn find_module() eine Ausnahme auslöst, wird diese an den Aufrufer weitergegeben und der Import abgebrochen.
Ein Loader-Objekt hat ebenfalls eine Methode
loader.load_module(fullname)
Diese Methode gibt das geladene Modul zurück oder löst eine Ausnahme aus, vorzugsweise ImportError, wenn keine bestehende Ausnahme weitergegeben wird. Wenn load_module() aufgefordert wird, ein Modul zu laden, das es nicht kann, soll ImportError ausgelöst werden.
In vielen Fällen können Finder und Loader ein und dasselbe Objekt sein: finder.find_module() würde einfach self zurückgeben.
Das Argument fullname beider Methoden ist der vollständig qualifizierte Modulname, z. B. „spam.eggs.ham“. Wie oben erläutert, wurde beim Aufruf von finder.find_module("spam.eggs.ham") das Modul „spam.eggs“ bereits importiert und zu sys.modules hinzugefügt. Die Methode find_module() wird jedoch nicht unbedingt immer während eines tatsächlichen Imports aufgerufen: Metatools, die Importabhängigkeiten analysieren (wie freeze, Installer oder py2exe), laden Module nicht tatsächlich, daher sollte ein Finder nicht vom übergeordneten Paket abhängen, das in sys.modules verfügbar ist.
Die Methode load_module() hat einige Verantwortlichkeiten, die sie *vor* der Ausführung von Code erfüllen muss
- Wenn ein vorhandenes Modulobjekt namens ‚fullname‘ in
sys.modulesvorhanden ist, muss der Loader dieses vorhandene Modul verwenden. (Andernfalls funktioniert die eingebaute Funktionreload()nicht richtig.) Wenn ein Modul namens ‚fullname‘ nicht insys.modulesexistiert, muss der Loader ein neues Modulobjekt erstellen und es zusys.moduleshinzufügen.Beachten Sie, dass das Modulobjekt *vor* der Ausführung des Modulcodes in
sys.modulesvorhanden sein muss. Dies ist entscheidend, da der Modulcode sich selbst (direkt oder indirekt) importieren kann; das vorherige Hinzufügen zusys.modulesverhindert im schlimmsten Fall eine unendliche Rekursion und im besten Fall ein mehrfaches Laden.Wenn das Laden fehlschlägt, muss der Loader alle Module entfernen, die er möglicherweise in
sys.moduleseingefügt hat. Wenn das Modul bereits insys.modulesvorhanden war, sollte der Loader es in Ruhe lassen. - Das Attribut
__file__muss gesetzt sein. Dies muss eine Zeichenkette sein, kann aber ein Dummy-Wert sein, z. B. „<frozen>“. Das Privileg, gar kein__file__-Attribut zu haben, ist für eingebaute Module reserviert. - Das Attribut
__name__muss gesetzt sein. Wenn manimp.new_module()verwendet, wird das Attribut automatisch gesetzt. - Wenn es sich um ein Paket handelt, muss die Variable
__path__gesetzt sein. Dies muss eine Liste sein, kann aber leer sein, wenn__path__für den Importer keine weitere Bedeutung hat (mehr dazu später). - Das Attribut
__loader__muss auf das Loader-Objekt gesetzt werden. Dies dient hauptsächlich der Introspektion und dem Neuladen, kann aber für importer-spezifische Extras verwendet werden, z. B. zum Abrufen von Daten, die mit einem Importer verbunden sind. - Das Attribut
__package__muss gesetzt sein (PEP 366).Wenn das Modul ein Python-Modul ist (im Gegensatz zu einem eingebauten Modul oder einer dynamisch geladenen Erweiterung), sollte es den Modulcode im globalen Namensraum des Moduls (
module.__dict__) ausführen.Hier ist ein minimales Muster für eine
load_module()-Methode# Consider using importlib.util.module_for_loader() to handle # most of these details for you. def load_module(self, fullname): code = self.get_code(fullname) ispkg = self.is_package(fullname) mod = sys.modules.setdefault(fullname, imp.new_module(fullname)) mod.__file__ = "<%s>" % self.__class__.__name__ mod.__loader__ = self if ispkg: mod.__path__ = [] mod.__package__ = fullname else: mod.__package__ = fullname.rpartition('.')[0] exec(code, mod.__dict__) return mod
Spezifikation Teil 2: Registrieren von Hooks
Es gibt zwei Arten von Import-Hooks: *Meta-Hooks* und *Pfad-Hooks*. Meta-Hooks werden zu Beginn der Importverarbeitung aufgerufen, bevor irgendeine andere Importverarbeitung stattfindet (damit Meta-Hooks die Verarbeitung von sys.path, eingefrorene Module oder sogar eingebaute Module überschreiben können). Um einen Meta-Hook zu registrieren, fügen Sie einfach das Finder-Objekt zu sys.meta_path (der Liste der registrierten Meta-Hooks) hinzu.
Pfad-Hooks werden als Teil der Verarbeitung von sys.path (oder package.__path__) aufgerufen, wenn ihr zugehöriges Pfadelement angetroffen wird. Ein Pfad-Hook wird registriert, indem eine Importer-Factory zu sys.path_hooks hinzugefügt wird.
sys.path_hooks ist eine Liste von aufrufbaren Objekten, die nacheinander überprüft werden, um festzustellen, ob sie ein bestimmtes Pfadelement verarbeiten können. Das aufrufbare Objekt wird mit einem Argument aufgerufen, dem Pfadelement. Das aufrufbare Objekt muss ImportError auslösen, wenn es das Pfadelement nicht verarbeiten kann, und ein Importer-Objekt zurückgeben, wenn es das Pfadelement verarbeiten kann. Beachten Sie, dass, wenn das aufrufbare Objekt ein Importer-Objekt für einen bestimmten sys.path-Eintrag zurückgibt, die integrierte Importmaschine nicht mehr aufgerufen wird, um diesen Eintrag zu verarbeiten, selbst wenn das Importer-Objekt später ein bestimmtes Modul nicht finden kann. Das aufrufbare Objekt ist typischerweise die Klasse des Import-Hooks, und daher wird die Klasse __init__() aufgerufen. (Dies ist auch der Grund, warum ImportError ausgelöst werden sollte: Eine __init__()-Methode kann nichts zurückgeben. Dies wäre mit einer __new__()-Methode in einer Klasse neuen Stils möglich, aber wir wollen keine Anforderungen darüber stellen, wie ein Hook implementiert wird.)
Die Ergebnisse der Pfad-Hook-Prüfungen werden in sys.path_importer_cache zwischengespeichert, einem Wörterbuch, das Pfadeinträge Importer-Objekten zuordnet. Der Cache wird überprüft, bevor sys.path_hooks durchsucht wird. Wenn es notwendig ist, einen erneuten Scan von sys.path_hooks zu erzwingen, ist es möglich, alle oder Teile von sys.path_importer_cache manuell zu löschen.
Genau wie sys.path selbst müssen die neuen sys-Variablen spezifische Typen haben
sys.meta_pathundsys.path_hooksmüssen Python-Listen sein.sys.path_importer_cachemuss ein Python-Wörterbuch sein.
Die Modifikation dieser Variablen an Ort und Stelle ist erlaubt, ebenso wie ihr Ersatz durch neue Objekte.
Pakete und die Rolle von __path__
Wenn ein Modul ein __path__-Attribut hat, behandelt der Importmechanismus es als Paket. Die Variable __path__ wird anstelle von sys.path verwendet, wenn Untermodule des Pakets importiert werden. Die Regeln für sys.path gelten daher auch für pkg.__path__. sys.path_hooks wird also ebenfalls konsultiert, wenn pkg.__path__ durchlaufen wird. Meta-Importer verwenden sys.path möglicherweise gar nicht, um ihre Arbeit zu erledigen, und ignorieren daher möglicherweise den Wert von pkg.__path__. In diesem Fall wird dennoch empfohlen, sie auf eine Liste zu setzen, die leer sein kann.
Optionale Erweiterungen des Importer-Protokolls
Das Importer-Protokoll definiert drei optionale Erweiterungen. Eine ist das Abrufen von Datendateien, die zweite die Unterstützung von Modul-Paketierungs-Tools und/oder Tools zur Analyse von Modulabhängigkeiten (z. B. Freeze), während die dritte die Unterstützung der Ausführung von Modulen als Skripte ist. Die beiden letzteren Kategorien von Tools laden Module normalerweise nicht tatsächlich, sie müssen nur wissen, ob und wo sie verfügbar sind. Alle drei Erweiterungen werden für Allzweck-Importer dringend empfohlen, können aber gefahrlos weggelassen werden, wenn diese Funktionen nicht benötigt werden.
Um Daten für beliebige „Dateien“ aus dem zugrunde liegenden Speicher-Backend abzurufen, können Loader-Objekte eine Methode namens get_data() bereitstellen
loader.get_data(path)
Diese Methode gibt die Daten als Zeichenkette zurück oder löst IOError aus, wenn die „Datei“ nicht gefunden wurde. Die Daten werden immer so zurückgegeben, als ob der „Binärmodus“ verwendet worden wäre – es gibt keine CRLF-Umwandlung von Textdateien zum Beispiel. Sie ist für Importer gedacht, die dateisystemähnliche Eigenschaften haben. Das ‚path‘-Argument ist ein Pfad, der durch Manipulation von module.__file__ (oder pkg.__path__-Elementen) mit den os.path.*-Funktionen konstruiert werden kann, zum Beispiel
d = os.path.dirname(__file__)
data = __loader__.get_data(os.path.join(d, "logo.gif"))
Der folgende Satz von Methoden kann implementiert werden, wenn Unterstützung für (z. B.) Freeze-ähnliche Tools gewünscht ist. Er besteht aus drei zusätzlichen Methoden, die, um es dem Aufrufer zu erleichtern, jeweils entweder alle implementiert sein sollten oder keine.
loader.is_package(fullname)
loader.get_code(fullname)
loader.get_source(fullname)
Alle drei Methoden sollten ImportError auslösen, wenn das Modul nicht gefunden wurde.
Die Methode loader.is_package(fullname) sollte True zurückgeben, wenn das durch ‚fullname‘ angegebene Modul ein Paket ist, und False, wenn es keines ist.
Die Methode loader.get_code(fullname) sollte das mit dem Modul assoziierte Code-Objekt zurückgeben oder None, wenn es sich um ein eingebautes oder Erweiterungsmodul handelt. Wenn der Loader das Code-Objekt nicht hat, aber den Quellcode hat, sollte er den kompilierten Quellcode zurückgeben. (Dies geschieht, damit unser Aufrufer nicht auch get_source() prüfen muss, wenn alles, was er braucht, das Code-Objekt ist.)
Die Methode loader.get_source(fullname) sollte den Quellcode des Moduls als Zeichenkette zurückgeben (mit Zeilenumbrüchen als Zeilenendungen) oder None, wenn die Quelle nicht verfügbar ist (er sollte aber dennoch ImportError auslösen, wenn das Modul vom Importer überhaupt nicht gefunden werden kann).
Um die Ausführung von Modulen als Skripte (PEP 338) zu unterstützen, müssen die oben genannten drei Methoden zum Auffinden des mit einem Modul verbundenen Codes implementiert werden. Zusätzlich zu diesen Methoden kann die folgende Methode bereitgestellt werden, um dem Modul runpy die korrekte Einstellung des Attributs __file__ zu ermöglichen
loader.get_filename(fullname)
Diese Methode sollte den Wert zurückgeben, der für __file__ gesetzt würde, wenn das benannte Modul geladen würde. Wenn das Modul nicht gefunden wird, sollte ImportError ausgelöst werden.
Integration in das „imp“-Modul
Die neuen Import-Hooks sind nicht einfach in die bestehenden Aufrufe von imp.find_module() und imp.load_module() integrierbar. Es ist fraglich, ob dies überhaupt möglich ist, ohne Code zu brechen; es ist besser, einfach eine neue Funktion zum imp-Modul hinzuzufügen. Die Bedeutung der bestehenden Aufrufe von imp.find_module() und imp.load_module() ändert sich von: „sie machen den integrierten Importmechanismus zugänglich“ zu „sie machen den grundlegenden *unhooked* integrierten Importmechanismus zugänglich“. Sie rufen einfach keine Import-Hooks auf. Eine neue Funktion des imp-Moduls wird vorgeschlagen (aber noch nicht implementiert) unter dem Namen get_loader(), die wie im folgenden Muster verwendet wird
loader = imp.get_loader(fullname, path)
if loader is not None:
loader.load_module(fullname)
Im Falle eines „grundlegenden“ Imports, der von der Funktion imp.find_module() behandelt würde, wäre das Loader-Objekt ein Wrapper für die aktuelle Ausgabe von imp.find_module(), und loader.load_module() würde imp.load_module() mit dieser Ausgabe aufrufen.
Beachten Sie, dass dieser Wrapper derzeit noch nicht implementiert ist, obwohl ein Python-Prototyp in der Skriptdatei test_importhooks.py (die Klasse ImpWrapper) enthalten ist, die dem Patch beiliegt.
Vorwärtskompatibilität
Bestehende __import__-Hooks rufen neue Hooks nicht magisch auf, es sei denn, sie rufen die ursprüngliche __import__-Funktion als Fallback auf. Zum Beispiel sind ihooks.py, iu.py und imputil.py in diesem Sinne nicht vorwärtskompatibel mit dieser PEP.
Offene Fragen
Module benötigen oft unterstützende Datendateien, um ihre Arbeit zu erledigen, insbesondere bei komplexen Paketen oder vollständigen Anwendungen. Die aktuelle Praxis ist im Allgemeinen, solche Dateien über sys.path (oder ein package.__path__-Attribut) zu lokalisieren. Dieser Ansatz funktioniert im Allgemeinen nicht für Module, die über einen Import-Hook geladen werden.
Es gibt eine Reihe möglicher Wege, dieses Problem anzugehen
- „Tun Sie das nicht“. Wenn ein Paket Datendateien über seinen
__path__lokalisieren muss, ist es nicht geeignet, über einen Import-Hook geladen zu werden. Das Paket kann immer noch auf einem Verzeichnis insys.pathliegen, wie derzeit, daher sollte dies kein großes Problem darstellen. - Lokalisieren Sie Datendateien von einem Standardort aus, anstatt relativ zur Moduldatei. Ein relativ einfacher Ansatz (der von Distutils unterstützt wird) wäre die Lokalisierung von Datendateien basierend auf
sys.prefix(odersys.exec_prefix). Zum Beispiel die Suche inos.path.join(sys.prefix, "data", package_name). - Import-Hooks könnten eine Standardmethode zum Zugriff auf Datendateien relativ zur Moduldatei bieten. Das Standardobjekt
zipimportbietet eine Methodeget_data(name), die den Inhalt der „Datei“ namensnameals Zeichenkette zurückgibt. Um Modulen den Zugriff auf das Importer-Objekt zu ermöglichen, fügtzipimportdem Modul auch ein Attribut__loader__hinzu, das das zum Laden des Moduls verwendetezipimport-Objekt enthält. Wenn ein solcher Ansatz verwendet wird, ist es wichtig, dass der Client-Code darauf achtet, nicht zu fehlschlagen, wenn die Methodeget_data()nicht verfügbar ist, sodass unklar ist, ob dieser Ansatz eine allgemeine Antwort auf das Problem bietet.
Auf python-dev wurde vorgeschlagen, dass es nützlich wäre, eine Liste verfügbarer Module von einem Importer und/oder eine Liste verfügbarer Datendateien für die Verwendung mit der Methode get_data() erhalten zu können. Das Protokoll könnte zwei zusätzliche Erweiterungen erhalten, sagen wir list_modules() und list_files(). Letzteres ist sinnvoll für Loader-Objekte mit einer get_data()-Methode. Es ist jedoch etwas unklar, welches Objekt list_modules() implementieren sollte: der Importer oder der Loader oder beide?
Diese PEP ist auf das Laden von Modulen aus alternativen Orten ausgerichtet: Sie bietet derzeit keine dedizierten Lösungen für das Laden von Modulen aus alternativen Dateiformaten oder mit alternativen Compilern. Im Gegensatz dazu bietet das Modul ihooks aus der Standardbibliothek eine ziemlich einfache Möglichkeit, dies zu tun. Das Quixote-Projekt [7] verwendet diese Technik, um PTL-Dateien so zu importieren, als wären sie normale Python-Module. Um dasselbe mit den neuen Hooks zu tun, müsste entweder ein neues Modul implementiert werden, das eine Teilmenge von ihooks als Importer neuen Stils implementiert, oder ein hookbares eingebautes Pfad-Importer-Objekt hinzugefügt werden.
Es gibt keine spezifische Unterstützung innerhalb dieses PEP für das „Stapeln“ von Hooks. Es ist zum Beispiel nicht offensichtlich, wie man einen Hook schreibt, um Module aus tar.gz Dateien zu laden, indem man separate Hooks kombiniert, um Module aus .tar und .gz Dateien zu laden. Allerdings gibt es keine Unterstützung für solches Stapeln in den bestehenden Hook-Mechanismen (weder die grundlegende „Ersetze __import__“-Methode, noch eines der bestehenden Import-Hook-Module) und daher ist diese Funktionalität keine offensichtliche Anforderung des neuen Mechanismus. Es könnte sich jedoch lohnen, sie als zukünftige Verbesserung in Betracht zu ziehen.
Es ist möglich (über sys.meta_path), Hooks hinzuzufügen, die laufen, bevor sys.path verarbeitet wird. Es gibt jedoch keine äquivalente Möglichkeit, Hooks hinzuzufügen, die laufen, nachdem sys.path verarbeitet wurde. Vorerst kann, wenn ein Hook nach der Verarbeitung von sys.path erforderlich ist, dies simuliert werden, indem eine beliebige „Cookie“-Zeichenkette am Ende von sys.path hinzugefügt wird und der erforderliche Hook über die normale sys.path_hooks-Verarbeitung mit diesem Cookie verknüpft wird. Langfristig wird der Pfadverarbeitungscode ein „echter“ Hook für sys.meta_path werden, und zu diesem Zeitpunkt wird es möglich sein, benutzerdefinierte Hooks entweder davor oder danach einzufügen.
Implementierung
Die PEP 302 Implementierung wurde ab Python 2.3a1 in Python integriert. Eine frühere Version ist als Patch #652586 [9] verfügbar, aber interessanterweise enthält die Ausgabe eine ziemlich detaillierte Historie der Entwicklung und des Designs.
PEP 273 wurde unter Verwendung der Import-Hooks von PEP 302 implementiert.
Referenzen und Fußnoten
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0302.rst
Zuletzt geändert: 2025-02-01 08:59:27 GMT