PEP 273 – Importieren von Modulen aus Zip-Archiven
- Autor:
- James C. Ahlstrom <jim at interet.com>
- Status:
- Final
- Typ:
- Standards Track
- Erstellt:
- 11-Okt-2001
- Python-Version:
- 2.3
- Post-History:
- 26-Okt-2001
Zusammenfassung
Diese PEP fügt die Fähigkeit hinzu, Python-Module *.py, *.py[co] und Pakete aus Zip-Archiven zu importieren. Der gleiche Code wird verwendet, um normale Verzeichnisimporte zu beschleunigen, sofern os.listdir verfügbar ist.
Hinweis
Zip-Importe wurden zu Python 2.3 hinzugefügt, aber die endgültige Implementierung verwendet einen anderen Ansatz als den in dieser PEP beschriebenen. Die Implementierung von 2.3 ist der SourceForge-Patch #652586 [1], der neue Import-Hooks hinzufügt, die in PEP 302 beschrieben werden.
Der Rest dieser PEP ist daher nur von historischem Interesse.
Spezifikation
Derzeit ist sys.path eine Liste von Verzeichnisnamen als Strings. Wenn diese PEP implementiert wird, kann ein Eintrag in sys.path ein String sein, der einen Zip-Archiv-Dateinamen angibt. Das Zip-Archiv kann eine Unterverzeichnisstruktur enthalten, um Paketimporte zu unterstützen. Das Zip-Archiv erfüllt Importanforderungen genauso wie ein Unterverzeichnis.
Die Implementierung erfolgt in C-Code im Python-Kern und funktioniert auf allen unterstützten Python-Plattformen.
Beliebige Dateien können im Zip-Archiv vorhanden sein, aber nur Dateien *.py und *.py[co] stehen für den Import zur Verfügung. Zip-Importe von dynamischen Modulen (*.pyd, *.so) sind nicht zulässig.
So wie sys.path derzeit Standardverzeichnisnamen enthält, wird auch ein Standard-Zip-Archivname hinzugefügt. Andernfalls gibt es keine Möglichkeit, alle Python-Bibliotheksdateien aus einem Archiv zu importieren.
Unterverzeichnis-Äquivalenz
Das Zip-Archiv muss exakt wie ein Unterverzeichnisbaum behandelt werden, damit wir Paketimporte basierend auf aktuellen und zukünftigen Regeln unterstützen können. Alle Zip-Daten werden aus dem zentralen Verzeichnis entnommen, die Daten müssen korrekt sein, und unsinnige Zip-Dateien werden nicht unterstützt.
Angenommen, sys.path enthält „/A/B/SubDir“ und „/C/D/E/Archive.zip“, und wir versuchen, modfoo aus dem Paket Q zu importieren. Dann generiert import.c eine Liste von Pfaden und Erweiterungen und sucht nach der Datei. Die Liste der generierten Pfade ändert sich für Zip-Importe nicht. Angenommen, import.c generiert den Pfad „/A/B/SubDir/Q/R/modfoo.pyc“. Dann generiert es auch den Pfad „/C/D/E/Archive.zip/Q/R/modfoo.pyc“. Das Finden des SubDir-Pfads ist exakt gleichwertig mit dem Finden von „Q/R/modfoo.pyc“ im Archiv.
Angenommen, Sie packen /A/B/SubDir/* und alle seine Unterverzeichnisse in ein Zip-Archiv. Dann erfüllt Ihr Zip-Archiv Importanforderungen genauso wie Ihr Unterverzeichnis.
Nun, nicht ganz. Sie können dynamische Module nicht aus einer Zip-Datei bedienen. Dynamische Module haben Erweiterungen wie .dll, .pyd und .so. Sie sind betriebssystemabhängig und können wahrscheinlich nur aus einer Datei geladen werden. Es könnte möglich sein, das dynamische Modul aus dem Zip-Archiv zu extrahieren, in eine normale Datei zu schreiben und zu laden. Aber das würde bedeuten, temporäre Dateien zu erstellen und sich mit all den dynload_*.c zu befassen, und das ist wahrscheinlich keine gute Idee.
Beim Versuch, *.pyc zu importieren, wird stattdessen *.pyo verwendet, wenn es nicht verfügbar ist. Und umgekehrt, wenn nach *.pyo gesucht wird. Wenn weder *.pyc noch *.pyo verfügbar sind oder wenn die magischen Nummern ungültig sind, wird *.py kompiliert und für den Import verwendet, aber die kompilierte Datei wird nicht gespeichert. Python würde sie normalerweise im selben Verzeichnis wie *.py speichern, aber wir wollen sicherlich nicht in die Zip-Datei schreiben. Wir könnten in das Verzeichnis des Zip-Archivs schreiben, aber das würde es unübersichtlich machen, was nicht gut ist, wenn es sich beispielsweise um /usr/bin handelt.
Das Fehlschlagen des Schreibens der kompilierten Dateien würde Zip-Importe sehr langsam machen, und der Benutzer würde wahrscheinlich nicht herausfinden, was falsch ist. Daher ist es am besten, *.pyc und *.pyo zusammen mit *.py in das Archiv zu legen.
Effizienz
Der einzige Weg, Dateien in einem Zip-Archiv zu finden, ist die lineare Suche. Für jede Zip-Datei in sys.path suchen wir einmal nach ihren Namen und legen die Namen plus andere relevante Daten in einem statischen Python-Dictionary ab. Der Schlüssel ist der Archivname aus sys.path, verbunden mit dem Dateinamen (einschließlich aller Unterverzeichnisse) innerhalb des Archivs. Dies ist exakt der Name, der von import.c generiert wird, und erleichtert die Suche.
Dieser Mechanismus wird auch verwendet, um normale Verzeichnisimporte (nicht Zip) zu beschleunigen. Siehe unten.
zlib
Komprimierte Zip-Archive erfordern zlib zur Dekompression. Vor allen anderen Importen versuchen wir einen Import von zlib. Der Import komprimierter Dateien schlägt mit der Meldung „missing zlib“ fehl, es sei denn, zlib ist verfügbar.
Bootstrapping
Python importiert site.py selbst, und dieses importiert os, nt, ntpath, stat und UserDict. Es importiert auch sitecustomize.py, das weitere Module importieren kann. Zip-Importe müssen verfügbar sein, bevor site.py importiert wird.
So wie es Standardverzeichnisse in sys.path gibt, muss es auch ein oder mehrere Standard-Zip-Archive geben.
Das Problem ist, wie der Name lauten soll. Der Name sollte mit der Python-Version verknüpft sein, damit die Python-Ausführungsdatei ihre entsprechenden Bibliotheken korrekt finden kann, auch wenn mehrere Python-Versionen auf demselben Rechner vorhanden sind.
Wir fügen einen Namen zu sys.path hinzu. Unter Unix ist das Verzeichnis sys.prefix + "/lib" und der Dateiname ist "python%s%s.zip" % (sys.version[0], sys.version[2]). Für Python 2.2 und das Präfix /usr/local ist also der Pfad /usr/local/lib/python2.2/ bereits in sys.path, und /usr/local/lib/python22.zip würde hinzugefügt werden. Unter Windows ist der Dateiname der vollständige Pfad zu python22.dll, wobei „dll“ durch „zip“ ersetzt wird. Der Zip-Archivname wird immer als zweites Element in sys.path eingefügt. Das erste ist das Verzeichnis von main.py (danke Tim).
Verzeichnis-Importe
Das statische Python-Dictionary, das zur Beschleunigung von Zip-Importen verwendet wird, kann auch zur Beschleunigung normaler Verzeichnisimporte verwendet werden. Für jeden Eintrag in sys.path, der kein Zip-Archiv ist, rufen wir os.listdir auf und fügen den Verzeichnisinhalt zum Dictionary hinzu. Anstatt dann fopen() in einer doppelten Schleife aufzurufen, prüfen wir einfach das Dictionary. Dies beschleunigt Importe erheblich. Wenn os.listdir nicht existiert, wird das Dictionary nicht verwendet.
Benchmarks
| Fall | Original 2.2a3 | Verwendung von os.listdir | Zip unkomprimiert | Zip komprimiert |
|---|---|---|---|---|
| 1 | 3.2 2.5 3.2->1.02 | 2.3 2.5 2.3->0.87 | 1.66->0.93 | 1.5->1.07 |
| 2 | 2.8 3.9 3.0->1.32 | Gleich wie Fall 1. | ||
| 3 | 5.7 5.7 5.7->5.7 | 2.1 2.1 2.1->1.8 | 1.25->0.99 | 1.19->1.13 |
| 4 | 9.4 9.4 9.3->9.35 | Gleich wie Fall 3. |
Fall 1: Lokale Festplatte C:, sys.path hat seinen Standardwert. Fall 2: Lokale Festplatte C:, Verzeichnis mit Dateien befindet sich am Ende von sys.path. Fall 3: Netzwerkfreigabe, sys.path hat seinen Standardwert. Fall 4: Netzwerkfreigabe, Verzeichnis mit Dateien befindet sich am Ende von sys.path.
Benchmarks wurden auf einem Pentium 4 Klon, 1,4 GHz, 256 MB durchgeführt. Die Maschine lief unter Windows 2000 mit einem Linux/Samba-Netzwerkserver. Zeiten sind in Sekunden und stellen die Zeit zum Importieren von etwa 100 Lib-Modulen dar. Fall 2 und 4 haben das „korrekte“ Verzeichnis ans Ende von sys.path verschoben. „Uncomp“ bedeutet unkomprimiertes Zip-Archiv, „Compr“ bedeutet komprimiert.
Die anfänglichen Zeiten sind nach einem Neustart des Systems; die Zeit nach „->“ ist die Zeit nach wiederholten Läufen. Die Importzeiten von C: nach einem Neustart sind für den „Original“-Fall eher stark variabel, aber realistischer.
Benutzerdefinierte Importe
Die Logik demonstriert die Fähigkeit, mit der Standard-Suche zu importieren, bis ein benötigtes Python-Modul (in diesem Fall os) verfügbar wird. Dies kann zum Bootstrapping benutzerdefinierter Importer verwendet werden. Wenn beispielsweise „importer()“ in __init__.py existiert, könnte es für Importe verwendet werden. Das „importer()“ kann os und andere Module frei importieren, und diese werden vom Standardmechanismus erfüllt. Diese PEP definiert keine benutzerdefinierten Importer, und dieser Hinweis dient nur zur Information.
Implementierung
Eine C-Implementierung ist als SourceForge-Patch 492105 verfügbar. Abgelöst durch Patch 652586 und aktuelles CVS. [2]
Eine neuere Version (aktualisiert für das aktuelle CVS von Paul Moore) ist 645650. Abgelöst durch Patch 652586 und aktuelles CVS. [3]
Eine konkurrierende Implementierung von Just van Rossum ist 652586, die die Grundlage für die endgültige Implementierung von PEP 302 bildet. PEP 273 wurde mit den Import-Hooks von PEP 302 implementiert. [1]
Referenzen
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0273.rst
Zuletzt geändert: 2025-02-01 08:55:40 GMT