PEP 3147 – PYC Repository Directories
- Autor:
- Barry Warsaw <barry at python.org>
- Status:
- Final
- Typ:
- Standards Track
- Erstellt:
- 16. Dez. 2009
- Python-Version:
- 3.2
- Post-History:
- 30. Jan. 2010, 25. Feb. 2010, 03. Mrz. 2010, 12. Apr. 2010
- Resolution:
- Python-Dev Nachricht
Zusammenfassung
Dieser PEP beschreibt eine Erweiterung des Python-Importmechanismus, die das gemeinsame Nutzen von Python-Quellcode-Dateien zwischen mehreren installierten, unterschiedlichen Versionen des Python-Interpreters verbessert. Dies geschieht, indem mehr als eine Byte-Kompilierungsdatei (.pyc-Dateien) neben der Python-Quelldatei (.py-Datei) abgelegt werden kann. Die hier beschriebene Erweiterung kann auch verwendet werden, um verschiedene Python-Kompilierungs-Caches zu unterstützen, wie z. B. JIT-Ausgaben, die von einer Unladen Swallow (PEP 3146)-aktivierten C Python erzeugt werden können.
Hintergrund
CPython kompiliert seinen Quellcode in „Bytecode“ und speichert diesen aus Leistungsgründen auf dem Dateisystem zwischen, wann immer sich die Quelldatei geändert hat. Dies beschleunigt das Laden von Python-Modulen erheblich, da die Kompilierungsphase umgangen werden kann. Wenn Ihre Quelldatei foo.py lautet, speichert CPython den Bytecode in einer foo.pyc-Datei direkt neben der Quelle.
Bytecode-Dateien enthalten zwei 32-Bit Big-Endian-Zahlen, gefolgt vom serialisierten [2] Code-Objekt. Die 32-Bit-Zahlen repräsentieren eine magische Zahl und einen Zeitstempel. Die magische Zahl ändert sich jedes Mal, wenn Python das Bytecode-Format ändert, z. B. durch Hinzufügen neuer Bytecodes zu seiner virtuellen Maschine. Dies stellt sicher, dass pyc-Dateien, die für frühere Versionen der VM erstellt wurden, keine Probleme verursachen. Der Zeitstempel wird verwendet, um sicherzustellen, dass die pyc-Datei mit der py-Datei übereinstimmt, die zu ihrer Erstellung verwendet wurde. Wenn entweder die magische Zahl oder der Zeitstempel nicht übereinstimmen, wird die py-Datei neu kompiliert und eine neue pyc-Datei geschrieben.
In der Praxis ist bekannt, dass pyc-Dateien zwischen Python-Hauptversionen nicht kompatibel sind. Eine Betrachtung von import.c [3] im Python-Quellcode zeigt, dass in den letzten Jahren jede neue CPython-Hauptversion die pyc-Magiczahl erhöht hat.
Begründung
Linux-Distributionen wie Ubuntu [4] und Debian [5] stellen ihren Benutzern gleichzeitig mehr als eine Python-Version zur Verfügung. Zum Beispiel können Ubuntu 9.10 Karmic Koala-Benutzer Python 2.5, 2.6 und 3.1 installieren, wobei Python 2.6 der Standard ist.
Dies führt zu einem Konflikt für von der System installierte Drittanbieter-Python-Quelldateien, da Sie eine einzelne Python-Quelldatei nicht gleichzeitig für mehr als eine Python-Version kompilieren können. Wenn Python eine pyc-Datei mit einer nicht übereinstimmenden Magiczahl findet, greift es auf den langsameren Prozess der Neukompilierung der Quelle zurück. Wenn Ihr System also eine /usr/share/python/foo.py installiert hat, würden zwei verschiedene Python-Versionen um die pyc-Datei konkurrieren und sie jedes Mal neu schreiben, wenn die Quelle kompiliert wird. (Die Standardbibliothek ist davon nicht betroffen, da mehrere Versionen der Standardbibliothek auf solchen Distributionen installiert *sind*.)
Darüber hinaus enthalten die Distribution-Pakete keine Python-Versionsnummern, um die Belastung der Betriebssystem-Packer für diese Distributionen zu verringern [6]; sie werden für alle auf dem System installierten Python-Versionen gemeinsam genutzt. Das Einfügen von Python-Versionsnummern in die Pakete wäre ein Wartungsalbtraum, da alle Pakete – *und ihre Abhängigkeiten* – jedes Mal aktualisiert werden müssten, wenn eine neue Python-Version zur Distribution hinzugefügt oder daraus entfernt wurde. Aufgrund der schieren Anzahl verfügbarer Pakete ist dieser Arbeitsaufwand nicht zu bewältigen.
(PEP 384 wurde vorgeschlagen, um binäre Kompatibilitätsprobleme von Drittanbieter-Erweiterungsmodulen über verschiedene Python-Versionen hinweg zu lösen.)
Da diese Distributionen keine pyc-Dateien gemeinsam nutzen können, wurden ausgeklügelte Mechanismen entwickelt, um die resultierenden pyc-Dateien an nicht gemeinsam genutzten Orten abzulegen, während der Quellcode weiterhin gemeinsam genutzt wird. Beispiele hierfür sind die symbolischen Link-basierten Debian-Regime python-support [8] und python-central [9]. Diese Ansätze führen zu weitaus komplizierteren, fragileren, undurchsichtigeren und fragmentierteren Richtlinien für die Bereitstellung von Python-Anwendungen für eine breite Nutzerbasis. Wohl mehr Benutzer erhalten Python von ihrem Betriebssystem-Anbieter als von Upstream-Tarballs. Daher ist die Lösung dieses pyc-Sharing-Problems für CPython für solche Anbieter eine hohe Priorität.
Dieser PEP schlägt eine Lösung für dieses Problem vor.
Vorschlag
Die Import-Maschinerie von Python wird erweitert, um Bytecode-Cache-Dateien in einem einzelnen Verzeichnis innerhalb jedes Python-Paketverzeichnisses zu schreiben und zu suchen. Dieses Verzeichnis wird __pycache__ genannt.
Außerdem enthalten pyc-Dateinamen einen magischen String (genannt „Tag“), der die Python-Version unterscheidet, für die sie kompiliert wurden. Dies ermöglicht es mehreren Byte-kompilierten Cache-Dateien, für eine einzelne Python-Quelldatei nebeneinander zu existieren.
Der magische Tag ist implementierungsabhängig, sollte aber den Implementierungsnamen und eine Kurzform der Versionsnummer enthalten, z. B. cpython-32. Er muss eindeutig unter allen Python-Versionen sein, und jedes Mal, wenn die magische Zahl erhöht wird, muss ein neuer magischer Tag definiert werden. Eine Beispiel-pyc-Datei für Python 3.2 ist daher foo.cpython-32.pyc.
Der magische Tag ist im imp-Modul über die Funktion get_tag() verfügbar. Dies ist parallel zur Funktion imp.get_magic().
Dieses Schema hat den zusätzlichen Vorteil, dass die Unübersichtlichkeit in einem Python-Paketverzeichnis reduziert wird.
Wenn eine Python-Quelldatei zum ersten Mal importiert wird, wird ein __pycache__-Verzeichnis im Paketverzeichnis erstellt, falls noch keines existiert. Die pyc-Datei für die importierte Quelle wird in das __pycache__-Verzeichnis geschrieben, wobei der magisch-tag-formatierte Name verwendet wird. Wenn die Erstellung des __pycache__-Verzeichnisses oder der pyc-Datei darin fehlschlägt, wird der Import trotzdem erfolgreich sein, genau wie in einer Welt vor PEP 3147.
Wenn die py-Quelldatei fehlt, wird die pyc-Datei im __pycache__-Verzeichnis ignoriert. Dies eliminiert das Problem von versehentlich veralteten pyc-Datei-Imports.
Aus Gründen der Abwärtskompatibilität unterstützt Python weiterhin pyc-only-Distributionen, jedoch nur dann, wenn sich die pyc-Datei im Verzeichnis befindet, in dem sich die py-Datei *befinden würde*, d. h. nicht im __pycache__-Verzeichnis. pyc-Dateien außerhalb von __pycache__ werden nur importiert, wenn die py-Quelldatei fehlt.
Werkzeuge wie py_compile [15] und compileall [16] werden erweitert, um PEP 3147-formatierte Layouts automatisch zu erstellen, aber sie werden eine Option haben, um pyc-only-Distributionslayouts zu erstellen.
Beispiele
Wie würde das in der Praxis aussehen?
Nehmen wir an, wir haben ein Python-Paket namens alpha, das ein Unterpaket namens beta enthält. Das Quellverzeichnislayout vor der Byte-Kompilierung könnte wie folgt aussehen:
alpha/
__init__.py
one.py
two.py
beta/
__init__.py
three.py
four.py
Nachdem dieses Paket mit Python 3.2 Byte-kompiliert wurde, würde das folgende Layout angezeigt werden:
alpha/
__pycache__/
__init__.cpython-32.pyc
one.cpython-32.pyc
two.cpython-32.pyc
__init__.py
one.py
two.py
beta/
__pycache__/
__init__.cpython-32.pyc
three.cpython-32.pyc
four.cpython-32.pyc
__init__.py
three.py
four.py
Hinweis: Die Reihenfolge der Auflistung kann je nach Plattform variieren.
Nehmen wir an, zwei neue Python-Versionen werden installiert, eine ist Python 3.3 und die andere ist Unladen Swallow. Nach der Byte-Kompilierung würde das Dateisystem wie folgt aussehen:
alpha/
__pycache__/
__init__.cpython-32.pyc
__init__.cpython-33.pyc
__init__.unladen-10.pyc
one.cpython-32.pyc
one.cpython-33.pyc
one.unladen-10.pyc
two.cpython-32.pyc
two.cpython-33.pyc
two.unladen-10.pyc
__init__.py
one.py
two.py
beta/
__pycache__/
__init__.cpython-32.pyc
__init__.cpython-33.pyc
__init__.unladen-10.pyc
three.cpython-32.pyc
three.cpython-33.pyc
three.unladen-10.pyc
four.cpython-32.pyc
four.cpython-33.pyc
four.unladen-10.pyc
__init__.py
three.py
four.py
Wie Sie sehen können, können beliebig viele pyc-Dateien nebeneinander existieren, solange die Python-Versionsidentifizierungszeichenkette eindeutig ist. Diese Identifizierungszeichenketten werden unten näher erläutert.
Eine angenehme Eigenschaft dieses Layouts ist, dass die __pycache__-Verzeichnisse generell ignoriert werden können, sodass eine normale Verzeichnislistung etwa so aussehen würde:
alpha/
__pycache__/
__init__.py
one.py
two.py
beta/
__pycache__/
__init__.py
three.py
four.py
Dies ist viel weniger unübersichtlich als selbst das heutige Python.
Python-Verhalten
Wenn Python nach einem zu importierenden Modul sucht (z. B. foo), kann es eine von mehreren Situationen vorfinden. Gemäß den aktuellen Python-Regeln bedeutet „übereinstimmende pyc“, dass die Magiczahl mit der Magiczahl des aktuellen Interpreters übereinstimmt und der Zeitstempel der Quelldatei exakt mit dem Zeitstempel in der pyc-Datei übereinstimmt.
Fall 0: Der stationäre Zustand
Wenn Python aufgefordert wird, das Modul foo zu importieren, sucht es entlang seines sys.path nach einer foo.py-Datei (oder einem foo-Paket, aber das ist für diese Diskussion nicht wichtig). Wenn gefunden, prüft Python, ob eine übereinstimmende __pycache__/foo.<magic>.pyc-Datei vorhanden ist, und wenn ja, wird diese pyc-Datei geladen.
Fall 1: Der erste Import
Wenn Python die foo.py-Datei findet, und die __pycache__/foo.<magic>.pyc-Datei fehlt, wird Python diese erstellen und bei Bedarf auch das __pycache__-Verzeichnis erstellen. Python wird die foo.py-Datei parsen und byte-kompilieren und den Bytecode in __pycache__/foo.<magic>.pyc speichern.
Fall 2: Der zweite Import
Wenn Python aufgefordert wird, das Modul foo ein zweites Mal zu importieren (in einem anderen Prozess natürlich), sucht es erneut entlang seines sys.path nach der foo.py-Datei. Wenn Python die foo.py-Datei findet, sucht es nach einer übereinstimmenden __pycache__/foo.<magic>.pyc und liest diese, liest den Bytecode und fährt wie gewohnt fort.
Fall 3: __pycache__/foo.<magic>.pyc ohne Quellcode
Es ist möglich, dass die foo.py-Datei irgendwie entfernt wurde, während die zwischengespeicherte pyc-Datei noch auf dem Dateisystem verblieben ist. Wenn die __pycache__/foo.<magic>.pyc-Datei existiert, aber die foo.py-Datei, die zu ihrer Erstellung verwendet wurde, nicht mehr vorhanden ist, löst Python beim Versuch, foo zu importieren, einen ImportError aus. Mit anderen Worten: Python importiert keine pyc-Datei aus dem Cache-Verzeichnis, es sei denn, die Quelldatei existiert.
Fall 4: Legacy-pyc-Dateien und Imports ohne Quellcode
Python ignoriert alle Legacy-pyc-Dateien, wenn eine Quelldatei daneben existiert. Das heißt, wenn eine foo.pyc-Datei neben der foo.py-Datei existiert, wird die pyc-Datei in allen Fällen ignoriert.
Um jedoch weiterhin Distributions ohne Quellcode zu unterstützen, importiert Python eine einzelne pyc-Datei, wenn die Quelldatei fehlt und sich die pyc-Datei dort befindet, wo sich die Quelldatei befinden würde.
Fall 5: schreibgeschützte Dateisysteme
Wenn sich die Quelle auf einem schreibgeschützten Dateisystem befindet oder das __pycache__-Verzeichnis oder die pyc-Datei anderweitig nicht geschrieben werden kann, gelten alle Regeln. Dies ist auch der Fall, wenn __pycache__ zufällig mit Berechtigungen geschrieben wird, die das Schreiben von enthaltenden pyc-Dateien nicht zulassen.
Flussdiagramm
Hier ist ein Flussdiagramm, das beschreibt, wie Module geladen werden.
Alternative Python-Implementierungen
Alternative Python-Implementierungen wie Jython [11], IronPython [12], PyPy [13], Pynie [14] und Unladen Swallow können ebenfalls das __pycache__-Verzeichnis verwenden, um für ihre Plattformen relevante Kompilierungsartefakte zu speichern. Zum Beispiel könnte Jython die Klassendatei für das Modul in __pycache__/foo.jython-32.class speichern.
Implementierungsstrategie
Diese Funktion ist für Python 3.2 vorgesehen und löst das Problem für diese und alle zukünftigen Versionen. Sie kann auf Python 2.7 zurückportiert werden. Anbieter können die Änderungen nach eigenem Ermessen auf frühere Distributionen zurückportieren. Für Backports dieser Funktion nach Python 2 kann bei Verwendung des Flags -U eine Datei wie foo.cpython-27u.pyc geschrieben werden.
Auswirkungen auf bestehenden Code
Die Übernahme dieses PEP wird bestehende Code und Idiome sowohl innerhalb als auch außerhalb von Python beeinflussen. Dieser Abschnitt zählt einige dieser Auswirkungen auf.
Verfügbarkeit von PEP 3147 erkennen
Der einfachste Weg, um festzustellen, ob Ihre Python-Version PEP 3147-Funktionalität bietet, ist die folgende Prüfung:
>>> import imp
>>> has3147 = hasattr(imp, 'get_tag')
__file__
In Python 3 zeigt das Attribut __file__ eines Moduls, wenn es importiert wird, auf seine Quell-py-Datei (in Python 2 zeigt es auf die pyc-Datei). Das __file__ eines Pakets zeigt auf die py-Datei für sein __init__.py. Z. B.:
>>> import foo
>>> foo.__file__
'foo.py'
# baz is a package
>>> import baz
>>> baz.__file__
'baz/__init__.py'
Nichts in diesem PEP würde die Semantik von __file__ ändern.
Dieser PEP schlägt die Hinzufügung eines Attributs __cached__ zu Modulen vor, das immer auf die tatsächliche pyc-Datei verweist, die gelesen oder geschrieben wurde. Wenn die Umgebungsvariable $PYTHONDONTWRITEBYTECODE gesetzt ist, die Option -B gegeben wird oder wenn die Quelle auf einem schreibgeschützten Dateisystem liegt, dann verweist das Attribut __cached__ auf den Ort, an dem die pyc-Datei geschrieben worden *wäre*, wenn sie nicht existiert hätte. Dieser Ort schließt natürlich das __pycache__-Unterverzeichnis in seinem Pfad ein.
Für alternative Python-Implementierungen, die keine pyc-Dateien unterstützen, kann das Attribut __cached__ auf Informationen verweisen, die sinnvoll sind. Z. B. unter Jython könnte dies die .class-Datei für das Modul sein: __pycache__/foo.jython-32.class. Einige Implementierungen verwenden möglicherweise mehrere kompilierte Dateien, um das Modul zu erstellen. In diesem Fall kann __cached__ ein Tupel sein. Der genaue Inhalt von __cached__ ist Python-implementierungsspezifisch.
Es wird empfohlen, dass Implementierungen das Attribut __cached__ auf None setzen, wenn nichts Sinnvolles berechnet werden kann.
py_compile und compileall
Python wird mit zwei Modulen geliefert, py_compile [15] und compileall [16], die das Kompilieren von Python-Modulen außerhalb der integrierten Import-Maschinerie unterstützen. py_compile hat insbesondere intimes Wissen über die Byte-Kompilierung, daher werden diese aktualisiert, um das neue Layout zu verstehen. Das Flag -b wird zu compileall hinzugefügt, um Legacy .pyc-Byte-kompilierte Dateinamen zu schreiben.
bdist_wininst und der Windows-Installer
Diese Werkzeuge kompilieren Module auch explizit bei der Installation. Wenn sie nicht py_compile und compileall verwenden, müssten sie ebenfalls modifiziert werden, um das neue Layout zu verstehen.
Prüfung von Dateierweiterungen
Es gibt einige Codes, die nach Dateien suchen, die auf .pyc enden, und einfach das letzte Zeichen abschneiden, um die übereinstimmende .py-Datei zu finden. Dieser Code wird offensichtlich fehlschlagen, sobald dieser PEP implementiert ist.
Um diesen Anwendungsfall zu unterstützen, fügen wir zwei neue Methoden zum imp-Paket hinzu [17]:
imp.cache_from_source(py_path)->pyc_pathimp.source_from_cache(pyc_path)->py_path
Alternative Implementierungen sind frei, diese Funktionen zu überschreiben, um sinnvolle Werte basierend auf ihrer eigenen Unterstützung für diesen PEP zurückzugeben. Diese Methoden dürfen None zurückgeben, wenn die Implementierung (oder der aktive PEP 302-Loader) aus irgendeinem Grund den entsprechenden Dateinamen nicht berechnen kann. Sie sollten keine Ausnahmen auslösen.
Backports
Für Python-Versionen vor 3.2 (und möglicherweise 2.7) ist es möglich, diesen PEP als Backport zu integrieren. In Python 3.2 (und möglicherweise 2.7) wird dieses Verhalten jedoch standardmäßig aktiviert sein und das alte Verhalten ersetzen. Backports müssen das alte Layout standardmäßig unterstützen. Wir schlagen vor, PEP 3147 durch die Verwendung einer Umgebungsvariable namens $PYTHONENABLECACHEDIR oder des Befehlszeilenschalters -Xenablecachedir zu unterstützen, um die Funktion zu aktivieren.
Makefiles und andere Abhängigkeitstools
Makefiles und andere Tools, die Abhängigkeiten von .pyc-Dateien berechnen (z. B. um die Quelle byte-zu-kompilieren, wenn die .pyc fehlt), müssen aktualisiert werden, um die neuen Pfade zu überprüfen.
Alternativen
Dieser Abschnitt beschreibt einige alternative Ansätze oder Details, die während der Entwicklung des PEP in Betracht gezogen und verworfen wurden.
PEP 304
Es gibt einige Überschneidungen zwischen den Zielen dieses PEP und PEP 304, der zurückgezogen wurde. PEP 304 würde es einem Benutzer jedoch ermöglichen, eine Schatten-Dateisystemhierarchie zu erstellen, in der pyc-Dateien gespeichert werden. Dieses Konzept einer Schattenhierarchie für pyc-Dateien könnte verwendet werden, um die Ziele dieses PEP zu erfüllen. Obwohl PEP 304 nicht angibt, warum er zurückgezogen wurde, haben Schattenverzeichnisse eine Reihe von Problemen. Der Speicherort der Schatten-pyc-Dateien wäre nicht leicht zu finden und würde von der korrekten und konsistenten Verwendung der Umgebungsvariable $PYTHONBYTECODE sowohl durch das System als auch durch Endbenutzer abhängen. Es gibt auch globale Auswirkungen, was bedeutet, dass das System zwar pyc-Dateien schattieren möchte, Benutzer dies aber möglicherweise nicht tun möchten, aber der PEP nur einen Alles-oder-Nichts-Ansatz definiert.
Als Beispiel für das Problem wird ein gängiges (wenn auch fragiles) Python-Idiom zum Auffinden von Datendateien verwendet, indem etwas wie folgt gemacht wird:
from os import dirname, join
import foo.bar
data_file = join(dirname(foo.bar.__file__), 'my.dat')
Dies wäre problematisch, da foo.bar.__file__ den Speicherort der pyc-Datei im Schattenverzeichnis liefert und es von dort aus möglicherweise nicht möglich ist, die my.dat-Datei relativ zum Quellverzeichnis zu finden.
Fat Byte-Kompilierungsdateien
Eine frühere Version dieses PEP beschrieb „fat“ Python-Bytecode-Dateien. Diese Dateien würden das Äquivalent mehrerer pyc-Dateien in einer einzigen pyf-Datei enthalten, mit einer Lookup-Tabelle, die auf der entsprechenden Magiczahl basiert. Dies war ein erweiterbares Dateiformat, sodass die ersten 5 parallelen Python-Implementierungen ziemlich effizient unterstützt werden konnten, aber mit Lookup-Tabellen zur Erweiterung, um pyf-Bytecode-Objekte so groß wie nötig zu skalieren.
Die fat Byte-Kompilierungsdateien waren ziemlich komplex und führten inhärent zu schwierigen Race Conditions, daher wurde die aktuelle Vereinfachung der Verwendung von Verzeichnissen vorgeschlagen. Das gleiche Problem tritt bei der Verwendung von Zip-Dateien als fat pyc-Dateiformat auf.
Mehrere Dateierweiterungen
Der PEP-Autor berücksichtigte auch einen Ansatz, bei dem mehrere dünne Byte-kompilierte Dateien am selben Ort lebten, aber unterschiedliche Dateierweiterungen zur Bezeichnung der Python-Version verwendeten. Z. B. foo.pyc25, foo.pyc26, foo.pyc31 usw. Dies wurde aufgrund der Unübersichtlichkeit, die mit dem Schreiben so vieler verschiedener Dateien verbunden ist, abgelehnt. Der Ansatz mit mehreren Erweiterungen macht es schwieriger (und zu einer fortlaufenden Aufgabe), alle Tools zu aktualisieren, die von der Dateierweiterung abhängen.
.pyc
Es wurde ein Vorschlag gemacht, das __pycache__-Verzeichnis .pyc oder einen anderen Dot-File-Namen zu nennen. Dies hätte auf *nix-Systemen den Effekt, dass das Verzeichnis versteckt wird. Es gibt viele Gründe, warum dies vom BDFL [20] abgelehnt wurde, einschließlich der Tatsache, dass Dot-Files nur auf einigen Plattformen speziell sind, und wir diese Benutzer tatsächlich *nicht* komplett verstecken wollen.
Referenzimplementierung
Die Arbeit an diesem Code wird in einem Bazaar-Branch auf Launchpad verfolgt [22], bis er für die Zusammenführung in Python 3.2 bereit ist. Der Arbeits-Diff kann auch angezeigt werden [23] und wird automatisch aktualisiert, sobald neue Änderungen hochgeladen werden.
Ein Rietveld-Code-Review-Issue [24] wurde am 01.04.2010 eröffnet (nein, das ist kein Aprilscherz :).
Referenzen
[21] importlib: https://docs.pythonlang.de/3.1/library/importlib.html
Danksagungen
Barry Warsaws ursprüngliche Idee waren "fat" Python-Bytecode-Dateien. Martin von Loewis überprüfte einen frühen Entwurf des PEP und schlug die Vereinfachung vor, traditionelle pyc- und pyo-Dateien in einem Verzeichnis zu speichern. Viele andere Personen überprüften frühe Versionen dieses PEP und lieferten nützliches Feedback, einschließlich, aber nicht beschränkt auf:
- David Malcolm
- Josselin Mouette
- Matthias Klose
- Michael Hudson
- Michael Vogt
- Piotr Ożarowski
- Scott Kitterman
- Toshio Kuratomi
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-3147.rst
Zuletzt geändert: 2025-02-01 08:55:40 GMT