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

Python Enhancement Proposals

PEP 528 – Ändern der Kodierung der Windows-Konsole zu 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

Inhaltsverzeichnis

Zusammenfassung

Historisch gesehen hat Python die ANSI-APIs für die Interaktion mit dem Windows-Betriebssystem verwendet, oft über C-Laufzeitfunktionen. Diese wurden jedoch lange zugunsten der UTF-16-APIs discouraged. Innerhalb des Betriebssystems wird der gesamte Text als UTF-16 dargestellt, und die ANSI-APIs führen die Kodierung und Dekodierung unter Verwendung der aktiven Codepage durch.

Dieses PEP schlägt vor, die Standard-Stream-Implementierung unter Windows zu ändern, um die Unicode-APIs zu verwenden. Dies ermöglicht es Benutzern, den vollen Bereich von Unicode-Zeichen an der Standard-Windows-Konsole zu drucken und einzugeben. Dies erfordert auch eine subtile Änderung der Art und Weise, wie der Tokenizer Text von Readline-Hooks parst.

Spezifische Änderungen

Hinzufügen von _io.WindowsConsoleIO

Derzeit wird eine Instanz von _io.FileIO verwendet, um die Dateideskriptoren zu wrappen, die Standardeingabe, -ausgabe und -fehler darstellen. Wir fügen eine neue Klasse (in C implementiert) _io.WindowsConsoleIO hinzu, die als rohes IO-Objekt fungiert und die Windows-Konsolenfunktionen verwendet, insbesondere ReadConsoleW und WriteConsoleW.

Diese Klasse wird verwendet, wenn das Legacy-Modus-Flag nicht aktiv ist, wenn ein Standard-Stream über einen Dateideskriptor geöffnet wird und der Stream ein Konsolenpuffer und kein umgeleiteter Dateistream ist. Andernfalls wird _io.FileIO wie heute verwendet.

Dies ist eine rohe (Byte-)IO-Klasse, die Text erfordert, der mit utf-8 kodiert ist und dann zu utf-16-le dekodiert und an die Windows-APIs übergeben wird. Ebenso werden Bytes, die von der Klasse gelesen werden, vom Betriebssystem als utf-16-le bereitgestellt und beim Rückgeben an Python in utf-8 konvertiert.

Die Verwendung einer ASCII-kompatiblen Kodierung ist erforderlich, um die Kompatibilität mit Code aufrechtzuerhalten, der TextIOWrapper umgeht und direkt ASCII-Bytes in die Standard-Streams schreibt (z. B. Twisted’s process_stdinreader.py). Code, der eine bestimmte Kodierung für die Standard-Streams außer ASCII annimmt, wird wahrscheinlich fehlschlagen.

Hinzufügen von _PyOS_WindowsConsoleReadline

Um die Eingabe von Unicode an der interaktiven Eingabeaufforderung zu ermöglichen, ist ein neuer Readline-Hook erforderlich. Die vorhandene Funktion PyOS_StdioReadline wird an die neue Funktion _PyOS_WindowsConsoleReadline delegieren, wenn von einem Dateideskriptor gelesen wird, der ein Konsolenpuffer ist und das Legacy-Modus-Flag nicht aktiv ist (die Logik sollte identisch mit der oben genannten sein).

Da die Readline-Schnittstelle eine 8-Bit-kodierte Zeichenkette ohne eingebettete Nullzeichen zurückgeben muss, transkodiert die Funktion _PyOS_WindowsConsoleReadline von utf-16-le, wie es vom Betriebssystem gelesen wird, in utf-8.

Die Funktion PyRun_InteractiveOneObject, die derzeit die Kodierung aus sys.stdin bezieht, wählt utf-8 aus, es sei denn, das Legacy-Modus-Flag ist aktiv. Dies kann erfordern, dass Readline-Hooks ihre Kodierungen auf utf-8 ändern oder den Legacy-Modus für korrektes Verhalten erfordern.

Hinzufügen eines Legacy-Modus

Das Starten von Python mit der Umgebungsvariable PYTHONLEGACYWINDOWSSTDIO wird gesetzt, aktiviert das Legacy-Modus-Flag, das das vorherige Verhalten vollständig wiederherstellt.

Alternative Ansätze

Das Paket win_unicode_console ist eine reine Python-Alternative zur Änderung des Standardverhaltens der Konsole. Es implementiert im Wesentlichen dieselben Änderungen wie hier beschrieben in reinem Python-Code.

Code, der möglicherweise fehlschlägt

Die folgenden Code-Muster können aufgrund dieser Änderung fehlschlagen oder ein anderes Verhalten aufweisen. Alle diese Code-Beispiele erfordern die explizite Wahl, ein rohes Dateiobjekt anstelle eines bequemeren Wrappers zu verwenden, der keine sichtbare Änderung verhindern würde.

Annahme der stdin/stdout-Kodierung

Code, der davon ausgeht, dass die von sys.stdin.buffer oder sys.stdout.buffer benötigte Kodierung 'mbcs' oder eine spezifischere Kodierung ist, funktioniert möglicherweise derzeit zufällig, könnte aber unter dieser Änderung auf Probleme stoßen. Zum Beispiel

>>> sys.stdout.buffer.write(text.encode('mbcs'))
>>> r = sys.stdin.buffer.read(16).decode('cp437')

Um diesen Code zu korrigieren, sollte die für TextIOWrapper angegebene Kodierung verwendet werden, entweder implizit oder explizit

>>> # Fix 1: Use wrapper correctly
>>> sys.stdout.write(text)
>>> r = sys.stdin.read(16)

>>> # Fix 2: Use encoding explicitly
>>> sys.stdout.buffer.write(text.encode(sys.stdout.encoding))
>>> r = sys.stdin.buffer.read(16).decode(sys.stdin.encoding)

Fehlerhafte Verwendung des Raw-Objekts

Code, der das rohe IO-Objekt verwendet und partielle Lese- und Schreibvorgänge nicht korrekt behandelt, kann betroffen sein. Dies ist besonders wichtig für das Lesen, da die Anzahl der gelesenen Zeichen niemals ein Viertel der zulässigen Bytes überschreitet, da es keine praktikable Möglichkeit gibt, zu verhindern, dass Eingaben wesentlich längere UTF-8-Strings kodieren.

>>> raw_stdin = sys.stdin.buffer.raw
>>> data = raw_stdin.read(15)
abcdefghijklm
b'abc'
# data contains at most 3 characters, and never more than 12 bytes
# error, as "defghijklm\r\n" is passed to the interactive prompt

Um diesen Code zu korrigieren, sollte der gepufferte Reader/Writer verwendet werden, oder der Aufrufer sollte weiterlesen, bis sein Puffer voll ist

>>> # Fix 1: Use the buffered reader/writer
>>> stdin = sys.stdin.buffer
>>> data = stdin.read(15)
abcedfghijklm
b'abcdefghijklm\r\n'

>>> # Fix 2: Loop until enough bytes have been read
>>> raw_stdin = sys.stdin.buffer.raw
>>> b = b''
>>> while len(b) < 15:
...     b += raw_stdin.read(15)
abcedfghijklm
b'abcdefghijklm\r\n'

Verwendung des Raw-Objekts mit kleinen Puffern

Code, der das rohe IO-Objekt verwendet und versucht, weniger als vier Zeichen zu lesen, erhält nun einen Fehler. Da es möglich ist, dass jedes einzelne Zeichen bis zu vier Bytes benötigt, wenn es in UTF-8 dargestellt wird, müssen Anfragen fehlschlagen

>>> raw_stdin = sys.stdin.buffer.raw
>>> data = raw_stdin.read(3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: must read at least 4 bytes

Die einzige Umgehungslösung ist die Übergabe eines größeren Puffers

>>> # Fix: Request at least four bytes
>>> raw_stdin = sys.stdin.buffer.raw
>>> data = raw_stdin.read(4)
a
b'a'
>>> >>>

(Das zusätzliche >>> ist auf den im Eingabepuffer verbleibenden Zeilenumbruch zurückzuführen und ist in dieser Situation zu erwarten.)


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

Zuletzt geändert: 2025-02-01 08:59:27 GMT