PEP 529 – Änderung der Windows-Dateisystemkodierung auf UTF-8
- Autor:
- Steve Dower <steve.dower at python.org>
- Status:
- Final
- Typ:
- Standards Track
- Erstellt:
- 27-Aug-2016
- Python-Version:
- 3.6
- Post-History:
- 01-Sep-2016, 04-Sep-2016
- Resolution:
- Python-Dev Nachricht
Zusammenfassung
Historisch gesehen verwendet Python die ANSI-APIs für die Interaktion mit dem Windows-Betriebssystem, oft über C-Laufzeitfunktionen. Diese wurden jedoch lange zugunsten der UTF-16-APIs verworfen. Innerhalb des Betriebssystems wird der gesamte Text als UTF-16 dargestellt, und die ANSI-APIs führen die Kodierung und Dekodierung mithilfe der aktiven Codepage durch. Weitere Details finden Sie unter Naming Files, Paths, and Namespaces.
Dieses PEP schlägt vor, die Standard-Dateisystemkodierung unter Windows auf utf-8 zu ändern und alle Dateisystemfunktionen so zu ändern, dass sie die Unicode-APIs für Dateisystempfade verwenden. Dies wirkt sich nicht auf Code aus, der Strings zur Darstellung von Pfaden verwendet. Diejenigen, die Bytes für Pfade verwenden, können nun alle gültigen Pfade in Windows-Dateisystemen korrekt round-trippen. Derzeit waren die Konvertierungen zwischen Unicode (im OS) und Bytes (in Python) verlustbehaftet und würden Zeichen außerhalb der aktiven Codepage des Benutzers nicht korrekt round-trippen.
Insbesondere hat dies keine Auswirkungen auf die Kodierung des Inhalts von Dateien. Diese werden weiterhin standardmäßig auf locale.getpreferredencoding() (für Textdateien) oder reine Bytes (für Binärdateien) gesetzt. Dies betrifft nur die Kodierung, die verwendet wird, wenn Benutzer ein Byte-Objekt an Python übergeben, das dann als Pfadname an das Betriebssystem weitergeleitet wird.
Hintergrund
Dateisystempfade werden fast universell als Text mit einer Kodierung dargestellt, die durch das Dateisystem bestimmt wird. In Python stellen wir diese Pfade über eine Reihe von Schnittstellen bereit, wie z. B. die Module os und io. Pfade können in beide Richtungen über diese Schnittstellen übergeben werden, d. h. vom Dateisystem zur Anwendung (z. B. os.listdir()) oder von der Anwendung zum Dateisystem (z. B. os.unlink()).
Wenn Pfade zwischen dem Dateisystem und der Anwendung übergeben werden, werden sie entweder als Byte-Blob durchgereicht oder mit os.fsencode() und os.fsdecode() in/von str konvertiert oder explizit mit sys.getfilesystemencoding() kodiert. Das Ergebnis der Kodierung eines Strings mit sys.getfilesystemencoding() ist ein Byte-Blob im nativen Format für das Standard-Dateisystem.
Unter Windows ist das native Format für das Dateisystem utf-16-le. Die empfohlenen Plattform-APIs für den Zugriff auf das Dateisystem akzeptieren und geben Text in diesem Format zurück. Vor Windows NT (und möglicherweise weiter zurück) war das native Format jedoch eine konfigurierbare Maschinenoption und es gab eine separate Reihe von APIs, um dieses Format zu akzeptieren. Die Option (die „aktive Codepage“) und diese APIs (die „*A-Funktionen“) existieren in neueren Versionen von Windows immer noch aus Gründen der Abwärtskompatibilität, obwohl neue Funktionalitäten oft nur eine UTF-16-LE-API (die „*W-Funktionen“) haben.
In Python wird str empfohlen, da es alle in Pfaden verwendeten Zeichen korrekt round-trippen kann (unter POSIX mit Surrogateescape-Handling; unter Windows, da str der nativen Darstellung zugeordnet wird). Unter Windows können Bytes nicht alle in Pfaden verwendeten Zeichen round-trippen, da Python intern die *A-Funktionen verwendet und die Kodierung daher „was auch immer die aktive Codepage ist“ ist. Da die aktive Codepage nicht alle Unicode-Zeichen darstellen kann, kann die Konvertierung eines Pfades in Bytes ohne Warnung oder verfügbare Anzeige Informationen verlieren.
Als Demonstration davon
>>> open('test\uAB00.txt', 'wb').close()
>>> import glob
>>> glob.glob('test*')
['test\uab00.txt']
>>> glob.glob(b'test*')
[b'test?.txt']
Das Unicode-Zeichen im zweiten Aufruf von glob wurde durch ein ‚?‘ ersetzt, was bedeutet, dass die Übergabe des Pfades zurück an das Dateisystem zu einem FileNotFoundError führt. Die gleichen Ergebnisse können mit os.listdir() oder jeder anderen Funktion beobachtet werden, die den Rückgabetyp an den Parametertyp anpasst.
Während eine vom Benutzer zugängliche Korrektur die durchgängige Verwendung von str ist, leiden POSIX-Systeme im Allgemeinen nicht unter Datenverlust, wenn Bytes ausschließlich verwendet werden, da Bytes die kanonische Darstellung sind. Selbst wenn die Kodierung nach irgendeinem Standard „falsch“ ist, wird das Dateisystem die Bytes immer noch dem Dateizuordnen. Die Nutzung dessen vermeidet die Kosten für Dekodierung und Neukodierung, sodass (theoretisch und nur unter POSIX) Code wie dieser aufgrund der Verwendung von b'.' schneller sein kann als die Verwendung von '.'
>>> for f in os.listdir(b'.'):
... os.stat(f)
...
Daher bevorzugen POSIX-orientierte Bibliothekautoren die Verwendung von Bytes zur Darstellung von Pfaden. Für einige Autoren ist dies auch eine Bequemlichkeit, da ihr Code bereits korrekt kodierte Bytes empfangen kann, während andere versuchen, die Portierung ihres Codes von Python 2 zu vereinfachen. Die Korrektheitsannahmen lassen sich jedoch nicht auf Windows übertragen, wo Unicode die kanonische Darstellung ist und Fehler auftreten können. Dieser potenzielle Datenverlust ist der Grund, warum die Verwendung von Byte-Pfaden unter Windows in Python 3.3 als veraltet galt – alle obigen Code-Schnipsel erzeugen unter Windows Deprecation-Warnungen.
Vorschlag
Derzeit ist die Standard-Dateisystemkodierung „mbcs“, ein Meta-Encoder, der die aktive Codepage verwendet. Wenn jedoch Bytes an das Dateisystem übergeben werden, durchlaufen sie die *A-APIs und das Betriebssystem kümmert sich um die Kodierung. In diesem Fall werden Pfade immer mit dem Äquivalent von „mbcs:replace“ kodiert, ohne dass Python die Möglichkeit hat, dies zu überschreiben oder zu ändern.
Dieser Vorschlag würde die Verwendung der *A-APIs vollständig entfernen und nur noch die *W-APIs aufrufen. Wenn Windows Pfade als str an Python zurückgibt, werden diese aus utf-16-le dekodiert und als Text zurückgegeben (in der minimalen Darstellung). Wenn Python-Code Pfade als bytes anfordert, werden die Pfade von utf-16-le in utf-8 unter Verwendung von surrogatepass transkodiert (Windows validiert keine Surrogate-Paare, daher können ungültige Surrogates in Dateinamen vorhanden sein). Ebenso, wenn Pfade als bytes bereitgestellt werden, werden diese von utf-8 in utf-16-le transkodiert und an die *W-APIs übergeben.
Die Verwendung von utf-8 wird nicht konfigurierbar sein, außer durch die Bereitstellung eines „Legacy-Modus“-Flags, um zum vorherigen Verhalten zurückzukehren.
Der Fehler-Modus surrogateescape gilt hier nicht, da es nicht darum geht, unsinnige Bytes zu erhalten. Jeder vom Betriebssystem zurückgegebene Pfad ist ein gültiges Unicode, während ungültige vom Benutzer erstellte Pfade einen Dekodierungsfehler auslösen sollten (derzeit würden diese OSError oder eine Unterklasse auslösen).
Die Wahl von UTF-8-Bytes (im Gegensatz zu UTF-16-LE-Bytes) dient dazu, die Fähigkeit zum Round-Tripping von Pfadnamen sicherzustellen und grundlegende Manipulationen (z. B. mit dem Modul os.path) bei Annahme einer ASCII-kompatiblen Kodierung zu ermöglichen. Die Verwendung von UTF-16-LE als Kodierung ist reiner, wird aber mehr Probleme verursachen als lösen.
Diese Änderung würde auch die Verwendung von Byte-Pfaden unter Windows wieder zulassen. Es ist keine Änderung der Semantik der Verwendung von Bytes als Pfad erforderlich – wie zuvor müssen sie mit der von sys.getfilesystemencoding() angegebenen Kodierung kodiert werden.
Spezifische Änderungen
Aktualisierung von sys.getfilesystemencoding
Entfernen des Standardwerts für Py_FileSystemDefaultEncoding und Setzen in initfsencoding() auf utf-8 oder, wenn der Legacy-Modus-Schalter aktiviert ist, auf mbcs.
Aktualisieren der Implementierungen von PyUnicode_DecodeFSDefaultAndSize() und PyUnicode_EncodeFSDefault(), um den utf-8-Codec zu verwenden oder, wenn der Legacy-Modus-Schalter aktiviert ist, den vorhandenen mbcs-Codec.
Hinzufügen von sys.getfilesystemencodeerrors
Da der Fehler-Modus nun zwischen surrogatepass und replace wechseln kann, benötigt Python-Code, der Kodierungen manuell durchführt, ebenfalls Zugriff auf den aktuellen Fehler-Modus. Dies umfasst die Implementierung von os.fsencode() und os.fsdecode(), die derzeit einen Fehler-Modus basierend auf dem Codec annehmen.
Hinzufügen einer öffentlichen Py_FileSystemDefaultEncodeErrors, ähnlich der vorhandenen Py_FileSystemDefaultEncoding. Der Standardwert unter Windows ist surrogatepass oder im Legacy-Modus replace. Der Standardwert auf allen anderen Plattformen ist surrogateescape.
Hinzufügen einer öffentlichen Funktion sys.getfilesystemencodeerrors(), die den aktuellen Fehler-Modus zurückgibt.
Aktualisieren der Implementierungen von PyUnicode_DecodeFSDefaultAndSize() und PyUnicode_EncodeFSDefault(), um die Variable für den Fehler-Modus anstelle von konstanten Strings zu verwenden.
Aktualisieren der Implementierungen von os.fsencode() und os.fsdecode(), um sys.getfilesystemencodeerrors() anstelle von Annahmen über den Modus zu verwenden.
Aktualisierung von path_converter
Aktualisieren des Pfadkonverters, um Byte- oder Pufferobjekte immer mithilfe von PyUnicode_DecodeFSDefaultAndSize() in Text zu dekodieren.
Ändern des Feldes narrow von einem char*-String in ein Flag, das angibt, ob das ursprüngliche Objekt Bytes war. Dies ist erforderlich für Funktionen, die Pfade im gleichen Typ zurückgeben müssen, wie sie ursprünglich bereitgestellt wurden.
Entfernen des ungenutzten ANSI-Codes
Entfernen aller Code-Pfade, die das Feld narrow verwenden, da diese von keinem Aufrufer mehr erreichbar sind. Diese werden nur in posixmodule.c verwendet. Andere Verwendungen von Pfaden sollten die Verwendung von Byte-Pfaden durch Dekodierung und Verwendung der *W-APIs ersetzen.
Hinzufügen eines Legacy-Modus
Hinzufügen eines Legacy-Modus-Flags, das durch die Umgebungsvariable PYTHONLEGACYWINDOWSFSENCODING oder durch einen Funktionsaufruf an sys._enablelegacywindowsfsencoding() aktiviert wird. Der Funktionsaufruf kann nur zum Aktivieren des Flags verwendet werden und sollte von Programmen so früh wie möglich nach der Initialisierung verwendet werden. Der Legacy-Modus kann während der Ausführung von Python nicht deaktiviert werden.
Wenn dieses Flag gesetzt ist, wird die Standard-Dateisystemkodierung auf mbcs anstelle von utf-8 und der Fehler-Modus auf replace anstelle von surrogatepass gesetzt. Pfade werden weiterhin in breite Zeichen dekodiert und es werden nur *W-APIs aufgerufen. Die von und an Python übergebenen Bytes werden jedoch so kodiert wie vor dieser Änderung.
Rückgängigmachen der Deprecation von Byte-Pfaden unter Windows
Die Verwendung von Bytes als Pfade unter Windows ist derzeit veraltet. Wir werden ankündigen, dass dies nicht mehr der Fall ist und dass Pfade, wenn sie als Bytes kodiert werden, das verwenden sollten, was von sys.getfilesystemencoding() zurückgegeben wird, anstelle der aktiven Codepage des Benutzers.
Beta-Experiment
Um bei der Ermittlung der Auswirkungen dieser Änderung zu helfen, schlagen wir vor, sie vorläufig auf 3.6.0b1 anzuwenden, mit der Absicht, eine endgültige Entscheidung vor 3.6.0b4 zu treffen.
Während der Experimentierphase werden die Meldungen über Dekodierungs- und Kodierungsfehler erweitert, um einen Link zu einer aktiven Online-Diskussion einzufügen und zur Meldung von Problemen aufzurufen.
Wenn beschlossen wird, die Funktionalität für 3.6.0b4 rückgängig zu machen, würde die Implementierungsänderung darin bestehen, das Legacy-Modus-Flag dauerhaft zu aktivieren, die Umgebungsvariable in PYTHONWINDOWSUTF8FSENCODING und die Funktion in sys._enablewindowsutf8fsencoding() zu ändern, um die Funktionalität im Einzelfall zu aktivieren, anstatt sie zu deaktivieren.
Es wird erwartet, dass, wenn wir die Änderung für 3.6 aufgrund von Kompatibilitätsproblemen nicht praktikabel umsetzen können, sie auch zu einem späteren Zeitpunkt in Python 3.x nicht mehr möglich sein wird.
Betroffene Module
Dieses PEP umfasst implizit alle Module innerhalb von Python, die entweder Pfadnamen an das Betriebssystem übergeben oder andernfalls sys.getfilesystemencoding() verwenden.
Ab 3.6.0a4 sind die folgenden Module modifizierungsbedürftig
os_overlapped_socketsubprocesszipimport
Die folgenden Module verwenden sys.getfilesystemencoding(), benötigen aber keine Modifikation
gc(nimmt bereits an, dass Bytes UTF-8 sind)grp(nicht für Windows kompiliert)http.server(enthält den Codec-Namen korrekt mit den übertragenen Daten)idlelib.editor(sollte nicht benötigt werden; hat Fallback-Handling)nis(nicht für Windows kompiliert)pwd(nicht für Windows kompiliert)spwd(nicht für Windows kompiliert)_ssl(nur für ASCII-Konstanten verwendet)tarfile(Code unter Windows nicht verwendet)_tkinter(nimmt bereits an, dass Bytes UTF-8 sind)wsgiref(wird als Standardkodierung für unbekannte Umgebungen angenommen)zipapp(Code unter Windows nicht verwendet)
Der folgende native Code verwendet eine der Kodierungs- oder Dekodierungsfunktionen, erfordert jedoch keine Modifikation
Parser/parsetok.c(Dokumentation spezifiziert bereitssys.getfilesystemencoding())Python/ast.c(Dokumentation spezifiziert bereitssys.getfilesystemencoding())Python/compile.c(undokumentiert, aber Python-Dateisystemkodierung impliziert)Python/errors.c(Dokumentation spezifiziert bereitsos.fsdecode())Python/fileutils.c(Code unter Windows nicht verwendet)Python/future.c(undokumentiert, aber Python-Dateisystemkodierung impliziert)Python/import.c(Dokumentation spezifiziert bereits UTF-8)Python/importdl.c(Code unter Windows nicht verwendet)Python/pythonrun.c(Dokumentation spezifiziert bereitssys.getfilesystemencoding())Python/symtable.c(undokumentiert, aber Python-Dateisystemkodierung impliziert)Python/thread.c(Code unter Windows nicht verwendet)Python/traceback.c(kodiert korrekt zum Vergleichen von Strings)Python/_warnings.c(Dokumentation spezifiziert bereitsos.fsdecode())
Abgelehnte Alternativen
Verwendung von Strict mbcs-Dekodierung
Dies ist im Wesentlichen dasselbe wie die vorgeschlagene Änderung, ändert jedoch sys.getfilesystemencoding() von utf-8 in mbcs (das dynamisch der aktiven Codepage zugeordnet wird).
Dieser Ansatz ermöglicht die Verwendung neuer Funktionalitäten, die nur als *W-APIs verfügbar sind, sowie die Erkennung von Kodierungs-/Dekodierungsfehlern. Anstatt beispielsweise Unicode-Zeichen stillschweigend durch ‚?‘ zu ersetzen, wäre es möglich, die Operation zu warnen oder fehlschlagen zu lassen.
Im Vergleich zur vorgeschlagenen Korrektur könnte dies einige neue Funktionalitäten ermöglichen, behebt jedoch keines der anfänglich beschriebenen Probleme. Neue Laufzeitfehler können einige Probleme offensichtlicher machen und zu Korrekturen führen, vorausgesetzt, die Bibliotheksbetreuer sind daran interessiert, Windows zu unterstützen und einen separaten Code-Pfad hinzuzufügen, um Dateisystempfade als Strings zu behandeln.
Die Kodierung mbcs ohne strenge Fehler zu machen, ist äquivalent dazu, dass der Legacy-Modus-Schalter standardmäßig aktiviert ist. Dies ist ein möglicher Vorgehen, wenn es zu erheblichen Brüchen im tatsächlichen Code kommt und eine Verlängerung der Deprecation-Periode erforderlich ist, aber dennoch der Wunsch nach Vereinfachungen im CPython-Quellcode besteht.
Byte-Pfade unter Windows als Fehler behandeln
Durch die vollständige Verhinderung der Verwendung von Byte-Pfaden unter Windows verhindern wir, dass Benutzer auf Kodierungsprobleme stoßen.
Die Motivation für dieses PEP ist jedoch, die Wahrscheinlichkeit zu erhöhen, dass unter POSIX geschriebener Code auch unter Windows korrekt funktioniert. Diese Alternative würde in die andere Richtung gehen und solchen Code vollständig inkompatibel machen. Da dies den Benutzern keinerlei Vorteile bringt, wird sie abgelehnt.
Byte-Pfade auf allen Plattformen als Fehler behandeln
Durch die Deprecation und anschließende Deaktivierung der Verwendung von Byte-Pfaden auf allen Plattformen verhindern wir, dass Benutzer auf Kodierungsprobleme stoßen, unabhängig davon, wo der Code ursprünglich geschrieben wurde. Dies würde einen vollständigen Deprecation-Zyklus erfordern, da es derzeit auf anderen Plattformen als Windows keine Warnungen gibt.
Dies wird wahrscheinlich als feindselige Handlung gegenüber Python-Entwicklern im Allgemeinen angesehen und wird daher derzeit abgelehnt.
Code, der fehlschlagen könnte
Die folgenden Code-Muster können aufgrund dieser Änderung fehlschlagen oder ein anderes Verhalten aufweisen. Jedes dieser Beispiele wäre in plattformübergreifend intendiertem Code fragil gewesen. Die vorgeschlagenen Korrekturen demonstrieren die kompatibelste Methode, um mit Pfadkodierungsproblemen über alle Plattformen und über mehrere Python-Versionen hinweg umzugehen.
Beachten Sie, dass all diese Beispiele in Python 3.3 und neueren Versionen Deprecation-Warnungen erzeugen.
Keine Verwaltung von Kodierungen über Grenzen hinweg
Code, der Kodierungen nicht verwaltet, wenn er Protokollgrenzen überschreitet, funktioniert möglicherweise derzeit zufällig, kann aber auf Probleme stoßen, wenn sich die Kodierung ändert. Beachten Sie, dass die Quelle von filename jede Funktion sein kann, die ein Byte-Objekt zurückgibt, wie im zweiten Beispiel unten veranschaulicht
>>> filename = open('filename_in_mbcs.txt', 'rb').read()
>>> text = open(filename, 'r').read()
Um diesen Code zu korrigieren, sollte die Kodierung der Bytes in filename entweder beim Lesen aus der Datei oder vor der Verwendung des Werts angegeben werden
>>> # Fix 1: Open file as text (default encoding)
>>> filename = open('filename_in_mbcs.txt', 'r').read()
>>> text = open(filename, 'r').read()
>>> # Fix 2: Open file as text (explicit encoding)
>>> filename = open('filename_in_mbcs.txt', 'r', encoding='mbcs').read()
>>> text = open(filename, 'r').read()
>>> # Fix 3: Explicitly decode the path
>>> filename = open('filename_in_mbcs.txt', 'rb').read()
>>> text = open(filename.decode('mbcs'), 'r').read()
Wenn der Ersteller von filename vom Benutzer von filename getrennt ist, ist die Kodierung eine wichtige Information, die aufgenommen werden muss
>>> some_object.filename = r'C:\Users\Steve\Documents\my_file.txt'.encode('mbcs')
>>> filename = some_object.filename
>>> type(filename)
<class 'bytes'>
>>> text = open(filename, 'r').read()
Um diesen Code für die beste Kompatibilität über Betriebssysteme und Python-Versionen hinweg zu korrigieren, sollte der Dateiname als str verfügbar gemacht werden
>>> # Fix 1: Expose as str
>>> some_object.filename = r'C:\Users\Steve\Documents\my_file.txt'
>>> filename = some_object.filename
>>> type(filename)
<class 'str'>
>>> text = open(filename, 'r').read()
Alternativ muss die für den Pfad verwendete Kodierung dem Benutzer zur Verfügung gestellt werden. Die Angabe von os.fsencode() (oder sys.getfilesystemencoding()) ist eine akzeptable Wahl, oder ein neues Attribut könnte mit der genauen Kodierung hinzugefügt werden
>>> # Fix 2: Use fsencode
>>> some_object.filename = os.fsencode(r'C:\Users\Steve\Documents\my_file.txt')
>>> filename = some_object.filename
>>> type(filename)
<class 'bytes'>
>>> text = open(filename, 'r').read()
>>> # Fix 3: Expose as explicit encoding
>>> some_object.filename = r'C:\Users\Steve\Documents\my_file.txt'.encode('cp437')
>>> some_object.filename_encoding = 'cp437'
>>> filename = some_object.filename
>>> type(filename)
<class 'bytes'>
>>> filename = filename.decode(some_object.filename_encoding)
>>> type(filename)
<class 'str'>
>>> text = open(filename, 'r').read()
Explizite Verwendung von „mbcs“
Code, der Text explizit mit ‚mbcs‘ kodiert, bevor er an Dateisystem-APIs übergeben wird, übergibt nun falsch kodierte Bytes. Beachten Sie, dass die Quelle von filename in diesem Beispiel nicht relevant ist, solange es sich um einen str handelt
>>> filename = open('files.txt', 'r').readline().rstrip()
>>> text = open(filename.encode('mbcs'), 'r')
Um diesen Code zu korrigieren, sollte der String ohne explizite Kodierung übergeben werden oder os.fsencode() verwendet werden
>>> # Fix 1: Do not encode the string
>>> filename = open('files.txt', 'r').readline().rstrip()
>>> text = open(filename, 'r')
>>> # Fix 2: Use correct encoding
>>> filename = open('files.txt', 'r').readline().rstrip()
>>> text = open(os.fsencode(filename), 'r')
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0529.rst
Zuletzt geändert: 2025-02-01 08:59:27 GMT