PEP 519 – Hinzufügen eines Dateisystempfad-Protokolls
- Autor:
- Brett Cannon <brett at python.org>, Koos Zevenhoven <k7hoven at gmail.com>
- Status:
- Final
- Typ:
- Standards Track
- Erstellt:
- 11. Mai 2016
- Python-Version:
- 3.6
- Post-History:
- 11. Mai 2016, 12. Mai 2016, 13. Mai 2016
- Resolution:
- Python-Dev Nachricht
Inhaltsverzeichnis
- Zusammenfassung
- Begründung
- Vorschlag
- Abwärtskompatibilität
- Implementierung
- Abgelehnte Ideen
- Andere Namen für die Methode des Protokolls
- Separate str/bytes-Methoden
- Bereitstellung eines
path-Attributs - Nur Strings von
__fspath__()zurückgeben lassen - Ein generischer String-Encoding-Mechanismus
__fspath__als Attribut- Spezifische Typ-Hinting-Unterstützung bereitstellen
- Bereitstellung von
os.fspathb() __fspath__()vom Instanz aufrufen lassen
- Danksagungen
- Referenzen
- Urheberrecht
Zusammenfassung
Diese PEP schlägt ein Protokoll vor, damit Klassen, die einen Dateisystempfad repräsentieren, eine str- oder bytes-Darstellung bereitstellen können. Änderungen an Pythons Standardbibliothek werden ebenfalls vorgeschlagen, um dieses Protokoll, wo angebracht, zu nutzen, um die Verwendung von Pfadobjekten zu erleichtern, wo historisch nur str- und/oder bytes-Dateisystempfade akzeptiert wurden. Das Ziel ist, die Migration von Benutzern zu reichhaltigen Pfadobjekten zu erleichtern und gleichzeitig eine einfache Möglichkeit zu bieten, mit Code zu arbeiten, der str- oder bytes erwartet.
Begründung
Historisch gesehen wurden in Python Dateisystempfade als Strings oder Bytes dargestellt. Diese Darstellungsform ist aus C's eigener Entscheidung entstanden, Dateisystempfade als const char * [3] darzustellen. Obwohl dies ein absolut brauchbares Format für Dateisystempfade ist, ist es nicht unbedingt optimal. Das Problem ist, dass, obwohl alle Dateisystempfade als Strings oder Bytes dargestellt werden können, nicht alle Strings oder Bytes einen Dateisystempfad darstellen. Dies kann zu Problemen führen, bei denen jeder z. B. String zu einem Dateisystempfad "duck-typed" wird, ob er tatsächlich einen Pfad darstellt oder nicht.
Um die Darstellung von Dateisystempfaden von ihrer Darstellung als Strings und Bytes auf eine reichhaltigere Objekt-Darstellung zu heben, wurde das Modul `pathlib` [4] in Python 3.4 über PEP 428 vorläufig eingeführt. Obwohl von einigen als Verbesserung gegenüber Strings und Bytes für Dateisystempfade angesehen, hat es unter mangelnder Akzeptanz gelitten. Typischerweise ist das Hauptproblem, das für die niedrige Akzeptanzrate genannt wird, die fehlende Unterstützung in der Standardbibliothek. Dieser Mangel an Unterstützung erforderte, dass Benutzer von `pathlib` Pfadobjekte manuell in Strings konvertieren, indem sie str(path) aufrufen, was viele als fehleranfällig empfanden.
Ein Problem bei der Konvertierung von Pfadobjekten in Strings ergibt sich daraus, dass der einzige generische Weg, eine String-Darstellung des Pfades zu erhalten, darin besteht, das Objekt an str() zu übergeben. Dies kann problematisch sein, wenn es blind geschieht, da fast alle Python-Objekte eine String-Darstellung haben, unabhängig davon, ob sie ein Pfad sind oder nicht, z. B. ergibt str(None) ein Ergebnis, das builtins.open() [5] gerne zum Erstellen einer neuen Datei verwendet.
Diese gesamte Situation wird durch das DirEntry-Objekt [8] verschärft. Während Pfadobjekte eine Darstellung haben, die mit str() extrahiert werden kann, stellen DirEntry-Objekte stattdessen ein path-Attribut bereit. Da es keine gemeinsame Schnittstelle zwischen Pfadobjekten, DirEntry und anderen Drittanbieter-Pfadbibliotheken gibt, ist dies zu einem Problem geworden. Eine Lösung, die es jedem pfaderstellenden Objekt ermöglicht, zu deklarieren, dass es ein Pfad ist, und eine Möglichkeit, eine Low-Level-Darstellung zu extrahieren, die alle Pfadobjekte unterstützen könnten, ist gewünscht.
Diese PEP schlägt dann die Einführung eines neuen Protokolls vor, dem von Objekten gefolgt werden soll, die Dateisystempfade darstellen. Die Bereitstellung eines Protokolls ermöglicht eine explizite Kennzeichnung, welche Objekte Dateisystempfade darstellen, sowie eine Möglichkeit, eine Low-Level-Darstellung zu extrahieren, die mit älteren APIs verwendet werden kann, die nur Strings oder Bytes unterstützen.
Diskussionen bezüglich Pfadobjekten, die zu dieser PEP führten, finden sich in mehreren Threads im Archiv der python-ideas Mailingliste [1] für die Monate März und April 2016 und in den Archiven der python-dev Mailingliste [2] im April 2016.
Vorschlag
Dieser Vorschlag ist in zwei Teile aufgeteilt. Ein Teil ist der Vorschlag eines Protokolls für Objekte, um Unterstützung für die Bereitstellung einer Dateisystempfad-Darstellung zu deklarieren und bereitzustellen. Der andere Teil befasst sich mit Änderungen an Pythons Standardbibliothek zur Unterstützung des neuen Protokolls. Diese Änderungen führen auch dazu, dass das Modul `pathlib` seinen provisorischen Status aufgibt.
Protokoll
Die folgende abstrakte Basisklasse definiert das Protokoll, damit ein Objekt als Pfadobjekt betrachtet werden kann
import abc
import typing as t
class PathLike(abc.ABC):
"""Abstract base class for implementing the file system path protocol."""
@abc.abstractmethod
def __fspath__(self) -> t.Union[str, bytes]:
"""Return the file system path representation of the object."""
raise NotImplementedError
Objekte, die Dateisystempfade darstellen, werden die Methode __fspath__() implementieren, die die str- oder bytes-Darstellung des Pfades zurückgibt. Die str-Darstellung ist die bevorzugte Low-Level-Pfad-Darstellung, da sie menschenlesbar ist und was Leute historisch als Pfade darstellen.
Änderungen an der Standardbibliothek
Es wird erwartet, dass die meisten APIs in Pythons Standardbibliothek, die derzeit einen Dateisystempfad akzeptieren, entsprechend aktualisiert werden, um Pfadobjekte zu akzeptieren (ob dies Code oder einfach eine Aktualisierung der Dokumentation erfordert, wird variieren). Die unten genannten Module verdienen jedoch spezifische Details, da sie entweder grundlegende Änderungen aufweisen, die die Nutzung von Pfadobjekten ermöglichen, oder Ergänzungen/Entfernungen von APIs beinhalten.
builtins
open() [5] wird aktualisiert, um Pfadobjekte zu akzeptieren, und weiterhin str und bytes zu akzeptieren.
os
Die Funktion fspath() wird mit der folgenden Semantik hinzugefügt
import typing as t
def fspath(path: t.Union[PathLike, str, bytes]) -> t.Union[str, bytes]:
"""Return the string representation of the path.
If str or bytes is passed in, it is returned unchanged. If __fspath__()
returns something other than str or bytes then TypeError is raised. If
this function is given something that is not str, bytes, or os.PathLike
then TypeError is raised.
"""
if isinstance(path, (str, bytes)):
return path
# Work from the object's type to match method resolution of other magic
# methods.
path_type = type(path)
try:
path = path_type.__fspath__(path)
except AttributeError:
if hasattr(path_type, '__fspath__'):
raise
else:
if isinstance(path, (str, bytes)):
return path
else:
raise TypeError("expected __fspath__() to return str or bytes, "
"not " + type(path).__name__)
raise TypeError("expected str, bytes or os.PathLike object, not "
+ path_type.__name__)
Die Funktionen os.fsencode() [6] und os.fsdecode() [7] werden aktualisiert, um Pfadobjekte zu akzeptieren. Da beide Funktionen ihre Argumente auf bytes bzw. str zwängen, werden sie aktualisiert, um __fspath__() aufzurufen, falls vorhanden, um das Pfadobjekt in eine str- oder bytes-Darstellung zu konvertieren, und dann ihre entsprechenden Zwangsoperationen durchzuführen, als ob der Rückgabewert von __fspath__() das ursprüngliche Argument für die jeweilige Zwangsfunktion gewesen wäre.
Die Einführung von os.fspath(), die Aktualisierungen von os.fsencode()/os.fsdecode() und die aktuelle Semantik von pathlib.PurePath bieten die notwendige Semantik, um die gewünschte Pfaddarstellung zu erhalten. Für ein Pfadobjekt können pathlib.PurePath/Path verwendet werden. Um die str- oder bytes-Darstellung ohne jegliche Zwangsmaßnahme zu erhalten, kann os.fspath() verwendet werden. Wenn ein str gewünscht wird und die Kodierung von bytes als die Standard-Dateisystemkodierung angenommen werden soll, dann sollte os.fsdecode() verwendet werden. Wenn eine bytes-Darstellung gewünscht wird und alle Strings mit der Standard-Dateisystemkodierung kodiert werden sollen, dann wird os.fsencode() verwendet. Diese PEP empfiehlt die Verwendung von Pfadobjekten, wann immer möglich, und den Rückgriff auf String-Pfade bei Bedarf und die Verwendung von bytes als letzte Option.
Eine andere Sichtweise ist eine Hierarchie von Dateisystempfad-Darstellungen (höchste zu niedrigste Ebene): Pfad → str → bytes. Die besprochenen Funktionen und Klassen können alle Objekte auf der gleichen Ebene der Hierarchie akzeptieren, aber sie unterscheiden sich darin, ob sie Objekte auf eine andere Ebene fördern oder de-gradieren. Die Klasse pathlib.PurePath kann einen str in ein Pfadobjekt fördern. Die Funktion os.fspath() kann ein Pfadobjekt in eine str- oder bytes-Instanz de-gradieren, je nachdem, was __fspath__() zurückgibt. Die Funktion os.fsdecode() de-gradiert ein Pfadobjekt zu einem String oder fördert ein bytes-Objekt zu einem str. Die Funktion os.fsencode() de-gradiert ein Pfad- oder String-Objekt zu bytes. Es gibt keine Funktion, die eine Möglichkeit bietet, ein Pfadobjekt direkt zu bytes zu de-gradieren, ohne die String-De-gradierung zu umgehen.
Das DirEntry-Objekt [8] erhält eine Methode __fspath__(). Diese gibt denselben Wert zurück, der derzeit im path-Attribut von DirEntry-Instanzen zu finden ist.
Die Protokoll-ABC wird dem os-Modul unter dem Namen os.PathLike hinzugefügt.
os.path
Die verschiedenen Pfadmanipulationsfunktionen von os.path [9] werden aktualisiert, um Pfadobjekte zu akzeptieren. Für polymorphe Funktionen, die sowohl Bytes als auch Strings akzeptieren, werden sie aktualisiert, um einfach os.fspath() zu verwenden.
Während der Diskussionen, die zu dieser PEP führten, wurde vorgeschlagen, os.path nicht zu aktualisieren, unter dem Argument „explizit ist besser als implizit“. Der Gedanke war, dass, da __fspath__() selbst polymorph ist, es besser sein könnte, Code, der mit os.path arbeitet, die Pfaddarstellung explizit aus Pfadobjekten extrahieren zu lassen. Es gibt auch die Überlegung, dass die Hinzufügung von Unterstützung so tief in die Low-Level-OS-APIs dazu führen wird, dass Code magisch Pfadobjekte unterstützt, ohne dass eine Dokumentationsaktualisierung erforderlich ist, was zu potenziellen Beschwerden führt, wenn es nicht funktioniert, unbemerkt vom Projektentwickler.
Aber die Ansicht dieser PEP ist, dass in diesem Fall „Praktikabilität über Reinheit“ steht. Um den Übergang zur Unterstützung von Pfadobjekten zu erleichtern, ist es besser, den Übergang so einfach wie möglich zu gestalten, als sich über unerwartete/undokumentierte Duck-Typing-Unterstützung für Pfadobjekte durch Projekte Gedanken zu machen.
Es gab auch den Vorschlag, dass os.path-Funktionen in einer engen Schleife verwendet werden könnten und der Overhead des Prüfens oder Aufrufens von __fspath__() zu kostspielig wäre. In diesem Szenario würden nur pfadverbrauchende APIs direkt aktualisiert und pfadmanipulierende APIs wie die in os.path unverändert bleiben. Dies würde von Bibliothekautoren verlangen, ihren Code zur Unterstützung von Pfadobjekten zu aktualisieren, wenn sie Pfadmanipulationen durchführten, aber wenn der Bibliotheks-Code den Pfad direkt weitergab, müsste die Bibliothek nicht aktualisiert werden. Es ist jedoch die Ansicht dieser PEP und von Guido, dass dies eine unnötige Sorge ist und die Leistung immer noch akzeptabel sein wird.
pathlib
Der Konstruktor für pathlib.PurePath und pathlib.Path wird aktualisiert, um PathLike-Objekte zu akzeptieren. Sowohl PurePath als auch Path werden weiterhin keine bytes-Pfaddarstellungen akzeptieren, und wenn __fspath__() bytes zurückgibt, wird eine Ausnahme ausgelöst.
Das path-Attribut wird entfernt, da es durch diese PEP redundant wird (es war in keiner veröffentlichten Version von Python enthalten und ist daher keine Abwärtskompatibilitätsproblematik).
C API
Die C-API erhält eine äquivalente Funktion zu os.fspath()
/*
Return the file system path representation of the object.
If the object is str or bytes, then allow it to pass through with
an incremented refcount. If the object defines __fspath__(), then
return the result of that method. All other types raise a TypeError.
*/
PyObject *
PyOS_FSPath(PyObject *path)
{
_Py_IDENTIFIER(__fspath__);
PyObject *func = NULL;
PyObject *path_repr = NULL;
if (PyUnicode_Check(path) || PyBytes_Check(path)) {
Py_INCREF(path);
return path;
}
func = _PyObject_LookupSpecial(path, &PyId___fspath__);
if (NULL == func) {
return PyErr_Format(PyExc_TypeError,
"expected str, bytes or os.PathLike object, "
"not %S",
path->ob_type);
}
path_repr = PyObject_CallFunctionObjArgs(func, NULL);
Py_DECREF(func);
if (!PyUnicode_Check(path_repr) && !PyBytes_Check(path_repr)) {
Py_DECREF(path_repr);
return PyErr_Format(PyExc_TypeError,
"expected __fspath__() to return str or bytes, "
"not %S",
path_repr->ob_type);
}
return path_repr;
}
Abwärtskompatibilität
Es gibt keine expliziten Abwärtskompatibilitätsprobleme. Sofern ein Objekt nicht zufällig bereits eine __fspath__()-Methode definiert, gibt es keinen Grund zu erwarten, dass der bestehende Code fehlschlägt oder seine Semantik implizit geändert wird.
Bibliotheken, die Pfadobjekte und eine Python-Version vor Python 3.6 und der Existenz von os.fspath() unterstützen möchten, können die Idiomatik von path.__fspath__() if hasattr(path, "__fspath__") else path verwenden.
Implementierung
Dies ist die Aufgabenliste für die Änderungen, die diese PEP für Python 3.6 vorschlägt
- Entfernen des
path-Attributs aus `pathlib` (erledigt) - Entfernen des provisorischen Status von `pathlib` (erledigt)
- Hinzufügen von
os.PathLike(Code und Docs erledigt) - Hinzufügen von
PyOS_FSPath()(Code und Docs erledigt) - Hinzufügen von
os.fspath()(erledigt <erledigt) - Aktualisieren von
os.fsencode()(erledigt) - Aktualisieren von
os.fsdecode()(erledigt) - Aktualisieren von
pathlib.PurePathundpathlib.Path(erledigt)- Hinzufügen von
__fspath__() - Hinzufügen von
os.PathLike-Unterstützung zu den Konstruktoren
- Hinzufügen von
- Hinzufügen von
__fspath__()zuDirEntry(erledigt) - Aktualisieren von
builtins.open()(erledigt) - Aktualisieren von
os.path(erledigt) - Hinzufügen eines Glossareintrags für „pfadähnlich“ (erledigt)
- Aktualisieren von „Was ist neu“ (erledigt)
Abgelehnte Ideen
Andere Namen für die Methode des Protokolls
Verschiedene Namen wurden während der Diskussionen, die zu dieser PEP führten, vorgeschlagen, darunter __path__, __pathname__ und __fspathname__. Am Ende schien man sich auf __fspath__ zu einigen, da es eindeutig, aber nicht unnötig lang ist.
Separate str/bytes-Methoden
Zu einem Zeitpunkt wurde vorgeschlagen, dass __fspath__() nur Strings zurückgeben und eine andere Methode namens __fspathb__() eingeführt werden sollte, um Bytes zurückzugeben. Die Idee dahinter ist, dass durch die Nicht-Polymorphie von __fspath__() die Handhabung potenzieller String- oder Byte-Darstellungen einfacher werden könnte. Die allgemeine Übereinkunft war jedoch, dass die Rückgabe von Bytes eher selten sein wird und dass die verschiedenen Funktionen im `os`-Modul die bessere Abstraktion darstellen, um direkte Aufrufe von __fspath__() zu fördern.
Bereitstellung eines path-Attributs
Um das Problem zu lösen, dass pathlib.PurePath nicht von str erbt, wurde ursprünglich vorgeschlagen, ein path-Attribut einzuführen, um zu spiegeln, was os.DirEntry bietet. Letztendlich wurde jedoch festgestellt, dass ein Protokoll dasselbe Ergebnis liefern würde, ohne direkt eine API offenzulegen, mit der die meisten Leute nie direkt interagieren müssen.
Nur Strings von __fspath__() zurückgeben lassen
Ein Großteil der Diskussionen, die zu dieser PEP führten, drehte sich darum, ob __fspath__() polymorph sein und bytes sowie str zurückgeben sollte oder nur str. Die allgemeine Stimmung dafür war, dass bytes aufgrund ihres inhärenten Mangels an Informationen über ihre Kodierung schwierig zu handhaben sind, und PEP 383 ermöglicht es, alle Dateisystempfade mit str unter Verwendung des surrogateescape-Handlers darzustellen. Daher wäre es besser, die Verwendung von str als Low-Level-Pfad-Darstellung für High-Level-Pfadobjekte zu erzwingen.
Schließlich wurde entschieden, dass die Verwendung von bytes zur Darstellung von Pfaden einfach nicht verschwinden wird und daher bis zu einem gewissen Grad unterstützt werden sollte. Die Hoffnung ist, dass sich die Leute für Pfadobjekte wie `pathlib` entscheiden werden und dies die Leute von der direkten Arbeit mit bytes wegführen wird.
Ein generischer String-Encoding-Mechanismus
Zu einem Zeitpunkt gab es eine Diskussion über die Entwicklung eines generischen Mechanismus zur Extraktion einer String-Darstellung eines Objekts, die eine semantische Bedeutung hat (__str__() gibt nicht unbedingt etwas von semantischer Bedeutung zurück, das über das hinausgeht, was für das Debugging nützlich sein mag). Letztendlich wurde dies als mangelndes motivierendes Bedürfnis über das hinaus, das diese PEP auf spezifische Weise zu lösen versucht, angesehen.
__fspath__ als Attribut
Es wurde kurz erwogen, __fspath__ als Attribut statt als Methode zu implementieren. Dies wurde aus zwei Gründen abgelehnt. Erstens wurden Protokolle historisch als „magische Methoden“ und nicht als „magische Methoden und Attribute“ implementiert. Zweitens gibt es keine Garantie, dass die Low-Level-Darstellung eines Pfadobjekts vorab berechnet wird, was die Benutzer potenziell irreführen könnte, dass keine teure Berechnung im Hintergrund stattfindet, falls das Attribut als Property implementiert wäre.
Dies knüpft auch indirekt an die Idee an, ein path-Attribut einzuführen, um dasselbe zu erreichen. Diese Idee hat jedoch ein zusätzliches Problem, nämlich dass versehentlich jedes Objekt mit einem path-Attribut dem Protokoll-Duck-Typing entspricht. Die Einführung einer neuen magischen Methode für das Protokoll hilft, ein versehentliches Opt-in in das Protokoll zu vermeiden.
Spezifische Typ-Hinting-Unterstützung bereitstellen
Es gab einige Überlegungen, eine generische typing.PathLike-Klasse bereitzustellen, die z. B. typing.PathLike[str] zur Angabe eines Typ-Hints für ein Pfadobjekt ermöglicht, das eine String-Darstellung zurückgibt. Obwohl potenziell nützlich, wurde der Nutzen als zu gering eingeschätzt, um die Typ-Hint-Klasse hinzuzufügen.
Dies beseitigte auch jeden Wunsch, eine Klasse im typing-Modul einzuführen, die die Vereinigung aller akzeptablen pfaderstellenden Typen darstellt, da dies leicht durch typing.Union[str, bytes, os.PathLike] dargestellt werden kann, und die Hoffnung ist, dass Benutzer sich langsam nur an Pfadobjekte gewöhnen werden.
Bereitstellung von os.fspathb()
Es wurde vorgeschlagen, dass, um die Struktur von z. B. os.getcwd()/os.getcwdb() zu spiegeln, os.fspath() nur str zurückgeben und eine andere Funktion namens os.fspathb() eingeführt werden sollte, die nur bytes zurückgibt. Dies wurde abgelehnt, da die Zwecke der *b()-Funktionen an die Abfrage des Dateisystems gebunden sind, wo die Notwendigkeit besteht, die rohen Bytes zurückzubekommen. Da diese PEP nicht direkt mit Daten auf einem Dateisystem arbeitet (aber potenziell könnte), wurde die Ansicht vertreten, dass diese Unterscheidung unnötig ist. Es wird auch angenommen, dass der Bedarf an reinen Bytes nicht häufig genug sein wird, um ihn auf so spezifische Weise wie os.fsencode() unterstützen zu müssen, da diese ähnliche Funktionalität bietet.
__fspath__() vom Instanz aufrufen lassen
Ein früherer Entwurf dieser PEP hatte os.fspath(), der path.__fspath__() anstelle von type(path).__fspath__(path) aufruft. Die Änderung erfolgte, um mit der Auflösung anderer magischer Methoden in Python konsistent zu sein.
Danksagungen
Vielen Dank an alle, die an den verschiedenen Diskussionen zu dieser PEP teilgenommen haben, die sich über python-ideas und python-dev erstreckten. Besonderer Dank geht an Stephen Turnbull für direktes Feedback zu frühen Entwürfen dieser PEP. Weiterer besonderer Dank gilt Koos Zevenhoven und Ethan Furman, nicht nur für Feedback zu frühen Entwürfen dieser PEP, sondern auch für die Mithilfe bei der Steuerung der Gesamtdiskussion zu diesem Thema über die beiden Mailinglisten.
Referenzen
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0519.rst
Zuletzt geändert: 2025-02-01 08:59:27 GMT