Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

PEP 383 – Nicht dekodierbare Bytes in Systemzeichenoberflächen

Autor:
Martin von Löwis <martin at v.loewis.de>
Status:
Final
Typ:
Standards Track
Erstellt:
22. April 2009
Python-Version:
3.1
Post-History:


Inhaltsverzeichnis

Zusammenfassung

Dateinamen, Umgebungsvariablen und Kommandozeilenargumente sind in POSIX als Zeichenketten definiert; die C-APIs erlauben jedoch die Übergabe beliebiger Bytes – unabhängig davon, ob diese einer bestimmten Kodierung entsprechen oder nicht. Dieses PEP schlägt ein Mittel zur Behandlung solcher Unregelmäßigkeiten vor, indem die Bytes in Zeichenketten eingebettet werden, so dass die ursprüngliche Byte-Zeichenkette rekonstruiert werden kann.

Begründung

Der C-Datentyp `char` wird häufig verwendet, um sowohl Zeichendaten als auch Bytes darzustellen. Bestimmte POSIX-Schnittstellen sind spezifiziert und weithin als auf Zeichendaten operierend bekannt, jedoch machen die Systemaufrufschnittstellen keine Annahmen über die Kodierung dieser Daten und leiten sie unverändert weiter. Mit Python 3 verwenden Zeichenketten eine Unicode-basierte interne Darstellung, was es schwierig macht, die Kodierung von Byte-Zeichenketten so zu ignorieren, wie es die C-Schnittstellen können.

Andererseits hat Microsoft Windows NT die ursprüngliche Designbeschränkung von Unix korrigiert und in seinen Systemschnittstellen explizit gemacht, dass diese Daten (Dateinamen, Umgebungsvariablen, Kommandozeilenargumente) tatsächlich Zeichendaten sind, indem eine Unicode-basierte API bereitgestellt wird (eine C-char-basierte für Abwärtskompatibilität beibehalten).

Für Python 3 ist eine vorgeschlagene Lösung, zwei Sätze von APIs bereitzustellen: eine Byte-orientierte und eine Zeichen-orientierte, wobei die Zeichen-orientierte nur alle Daten nicht vollständig darstellen kann. Für Windows wäre die Situation genau umgekehrt: Die Byte-orientierte Schnittstelle kann nicht alle Daten darstellen; nur die Zeichen-orientierte API kann das. Infolgedessen müssen Bibliotheken und Anwendungen, die alle Benutzerdaten plattformübergreifend unterstützen wollen, eine Mischung aus Bytes und Zeichen akzeptieren, genau auf die Weise, die zu endlosen Problemen für Python 2.x geführt hat.

Mit diesem PEP wird eine einheitliche Behandlung dieser Daten als Zeichen möglich. Die Einheitlichkeit wird durch die Verwendung spezifischer Kodierungsalgorithmen erreicht, was bedeutet, dass die Daten auf POSIX-Systemen nur dann wieder in Bytes umgewandelt werden können, wenn dieselbe Kodierung verwendet wird.

Die Möglichkeit, solche Zeichenketten einheitlich zu behandeln, ermöglicht es Anwendungsschreibern, sich von betriebssystemspezifischen Details zu abstrahieren und verringert das Risiko, dass eine API fehlschlägt, während die andere funktioniert hätte.

Spezifikation

Unter Windows verwendet Python die Wide-Character-APIs, um auf zeichenorientierte APIs zuzugreifen, was eine direkte Konvertierung der Umgebungsdaten in Python `str`-Objekte ermöglicht (PEP 277).

Auf POSIX-Systemen wendet Python derzeit die Kodierung des Locales an, um die Byte-Daten in Unicode zu konvertieren, und schlägt fehl für Zeichen, die nicht dekodiert werden können. Mit diesem PEP werden nicht dekodierbare Bytes >= 128 als einzelne Surrogatcodes U+DC80..U+DCFF dargestellt. Bytes unter 128 lösen Ausnahmen aus; siehe die Diskussion unten.

Zur Konvertierung nicht dekodierbarer Bytes wird ein neuer Fehlerbehandler (PEP 293) namens „surrogateescape“ eingeführt, der diese Surrogate erzeugt. Beim Kodieren wandelt der Fehlerbehandler das Surrogat zurück in das entsprechende Byte. Dieser Fehlerbehandler wird in jeder API verwendet, die Dateinamen, Kommandozeilenargumente oder Umgebungsvariablen empfängt oder erzeugt.

Die Schnittstelle des Fehlerbehandlers wird erweitert, um es dem `encode`-Fehlerbehandler zu ermöglichen, sofort Byte-Zeichenketten zurückzugeben, zusätzlich zur Rückgabe von Unicode-Zeichenketten, die dann erneut kodiert werden (siehe auch die Diskussion unten).

Byte-orientierte Schnittstellen, die in Python 3.0 bereits vorhanden sind, werden von dieser Spezifikation nicht beeinflusst. Sie werden weder erweitert noch als veraltet markiert.

Externe Bibliotheken, die mit Dateinamen arbeiten (wie z. B. GUI-Dateiauswahlen), sollten diese ebenfalls gemäß dem PEP kodieren.

Diskussion

Diese „surrogateescape“-Kodierung basiert auf der Idee von Markus Kuhn, die er UTF-8b nannte [3].

Während eine einheitliche API für nicht dekodierbare Bytes bereitgestellt wird, hat diese Schnittstelle die Einschränkung, dass die gewählte Darstellung nur dann „funktioniert“, wenn die Daten auch mit dem „surrogateescape“-Fehlerbehandler wieder in Bytes umgewandelt werden. Die Daten mit der Locale-Kodierung und dem (Standard) `strict`-Fehlerbehandler zu kodieren, löst eine Ausnahme aus; sie mit UTF-8 zu kodieren, erzeugt unsinnige Daten.

Daten, die aus anderen Quellen stammen, können mit den von diesem PEP erzeugten Daten in Konflikt geraten. Die Behandlung solcher Konflikte liegt außerhalb des Anwendungsbereichs dieses PEP.

Dieses PEP ermöglicht das „Schmuggeln“ von Bytes in Zeichenketten. Dies wäre ein Sicherheitsrisiko, wenn die Bytes sicherheitsrelevant sind, wenn sie als Zeichen auf einem Zielsystem interpretiert werden, wie z. B. Pfadtrennzeichen. Aus diesem Grund lehnt das PEP das Einschleusen von Bytes unter 128 ab. Wenn das Zielsystem EBCDIC verwendet, können solche eingeschmuggelten Bytes immer noch ein Sicherheitsrisiko darstellen und z. B. Klammern oder den Backslash einschleusen. Python unterstützt derzeit kein EBCDIC, daher sollte dies in der Praxis kein Problem darstellen. Jeder, der Python auf ein EBCDIC-System portiert, möchte möglicherweise die Fehlerbehandler anpassen oder andere Ansätze finden, um die Sicherheitsrisiken anzugehen.

Kodierungen, die nicht ASCII-kompatibel sind, werden von dieser Spezifikation nicht unterstützt; Bytes im ASCII-Bereich, die nicht dekodiert werden können, lösen eine Ausnahme aus. Es ist allgemein anerkannt, dass solche Kodierungen nicht als Locale-Zeichensätze verwendet werden sollten.

Für die meisten Anwendungen gehen wir davon aus, dass sie Daten, die sie von einer Systemoberfläche erhalten, schließlich wieder an dieselben Systemoberflächen übergeben. Eine Anwendung, die z. B. `os.listdir()` aufruft, wird die Ergebniszeichenketten wahrscheinlich wieder an APIs wie `os.stat()` oder `open()` übergeben, die sie dann zurück in ihre ursprüngliche Byte-Darstellung kodieren. Anwendungen, die die ursprünglichen Byte-Zeichenketten verarbeiten müssen, können diese durch Kodieren der Zeichenketten mit der Dateisystemkodierung erhalten und „surrogateescape“ als Namen des Fehlerbehandlers übergeben. Eine Funktion, die wie `os.listdir` funktioniert, außer dass sie Bytes akzeptiert und zurückgibt, würde beispielsweise wie folgt geschrieben werden:

def listdir_b(dirname):
    fse = sys.getfilesystemencoding()
    dirname = dirname.decode(fse, "surrogateescape")
    for fn in os.listdir(dirname):
        # fn is now a str object
        yield fn.encode(fse, "surrogateescape")

Die Erweiterung der `encode`-Fehlerbehandler-Schnittstelle, die von diesem PEP vorgeschlagen wird, ist notwendig, um den Fehlerbehandler „surrogateescape“ zu implementieren, da erforderliche Byte-Sequenzen, die nicht aus Ersetzungs-Unicode generiert werden können. Die aktuelle Schnittstelle des `encode`-Fehlerbehandlers erfordert jedoch Ersetzungs-Unicode anstelle des nicht kodierbaren Unicode aus der Quellzeichenkette. Dann wird dieser Ersetzungs-Unicode sofort kodiert. Bei einigen Fehlerbehandlern, wie dem hier vorgeschlagenen „surrogateescape“, ist es auch einfacher und effizienter, wenn der Fehlerbehandler eine vorab kodierte Byte-Zeichenkette liefert, anstatt ihn zu zwingen, Unicode zu berechnen, aus dem der Encoder die gewünschten Bytes erzeugen würde.

Einige alternative Ansätze wurden vorgeschlagen

  • Erstellung einer neuen String-Unterklasse, die eingebettete Bytes unterstützt
  • Verwendung unterschiedlicher Escape-Schemata, wie z. B. Escaping mit einem NUL-Zeichen oder Mapping auf seltene Zeichen.

Von diesen Vorschlägen hat der Ansatz, jedes Byte XX mit der Sequenz U+0000 U+00XX zu escapen, den Nachteil, dass die Kodierung zu UTF-8 ein NUL-Byte in der UTF-8-Sequenz einführt. Infolgedessen können C-Bibliotheken dies als String-Terminierung interpretieren, obwohl der String weitergeht. Insbesondere die GTK-Bibliotheken schneiden Text in diesem Fall ab; andere Bibliotheken können ähnliche Probleme aufweisen.

Referenzen


Quelle: https://github.com/python/peps/blob/main/peps/pep-0383.rst

Zuletzt geändert: 2025-02-01 08:55:40 GMT