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

Python Enhancement Proposals

PEP 3138 – Stringrepräsentation in Python 3000

Autor:
Atsuo Ishimoto <ishimoto at gembook.org>
Status:
Final
Typ:
Standards Track
Erstellt:
05-Mai-2008
Python-Version:
3.0
Post-History:
05-Mai-2008, 05-Jun-2008

Inhaltsverzeichnis

Zusammenfassung

Dieses PEP schlägt eine neue Stringrepräsentationsform für Python 3000 vor. In Python vor Python 3000 konvertierte die eingebaute Funktion repr() beliebige Objekte in druckbare ASCII-Strings für Debugging und Protokollierung. Für Python 3000 sollte eine breitere Palette von Zeichen, basierend auf dem Unicode-Standard, als ‘druckbar’ betrachtet werden.

Motivation

Das aktuelle repr() konvertiert 8-Bit-Strings unter Verwendung des folgenden Algorithmus nach ASCII.

  • Konvertiere CR, LF, TAB und ‘\’ zu ‘\r’, ‘\n’, ‘\t’, ‘\\’.
  • Konvertiere andere nicht druckbare Zeichen (0x00-0x1f, 0x7f) und Nicht-ASCII-Zeichen (>= 0x80) zu ‘\xXX’.
  • Maskiere Anführungszeichen (Apostroph, ‘) mit einem Backslash und füge das Anführungszeichen am Anfang und Ende hinzu.

Für Unicode-Strings werden folgende zusätzliche Konvertierungen durchgeführt.

  • Konvertiere führende Ersatzzeichenpaare ohne nachfolgendes Zeichen (0xd800-0xdbff, aber nicht gefolgt von 0xdc00-0xdfff) zu ‘\uXXXX’.
  • Konvertiere 16-Bit-Zeichen (>= 0x100) zu ‘\uXXXX’.
  • Konvertiere 21-Bit-Zeichen (>= 0x10000) und Ersatzzeichenpaare zu ‘\U00xxxxxx’.

Dieser Algorithmus konvertiert jeden String in druckbares ASCII, und repr() wird als praktische und sichere Methode zum Drucken von Strings für Debugging oder Protokollierung verwendet. Obwohl alle Nicht-ASCII-Zeichen maskiert werden, spielt dies keine Rolle, wenn die meisten Zeichen des Strings ASCII sind. Aber für andere Sprachen, wie z.B. Japanisch, wo die meisten Zeichen eines Strings nicht ASCII sind, ist dies sehr unpraktisch.

Wir können print(aJapaneseString) verwenden, um einen lesbaren String zu erhalten, aber wir haben keine ähnliche Umgehungslösung für das Drucken von Strings aus Sammlungen wie Listen oder Tupeln. print(listOfJapaneseStrings) verwendet repr(), um den zu druckenden String zu erstellen, sodass die resultierenden Strings immer hex-maskiert sind. Oder wenn open(japaneseFilename) eine Ausnahme auslöst, ist die Fehlermeldung etwas wie IOError: [Errno 2] No such file or directory: '\u65e5\u672c\u8a9e', was nicht hilfreich ist.

Python 3000 hat viele nützliche Funktionen für nicht-lateinische Benutzer, wie z.B. Nicht-ASCII-Bezeichner, daher wäre es hilfreich, wenn Python in einer ähnlichen Weise für die druckbare Ausgabe voranschreiten könnte.

Einige Benutzer könnten besorgt sein, dass solche Ausgaben ihre Konsole durcheinanderbringen, wenn sie Binärdaten wie Bilder drucken. Aber dies ist in der Praxis unwahrscheinlich, da Bytes und Strings in Python 3000 unterschiedliche Typen sind, sodass das Drucken eines Bildes auf die Konsole diese nicht durcheinanderbringt.

Dieses Problem wurde einst von Hye-Shik Chang [1] diskutiert, aber abgelehnt.

Spezifikation

  • Füge eine neue Funktion zur Python C API hinzu int Py_UNICODE_ISPRINTABLE (Py_UNICODE ch). Diese Funktion gibt 0 zurück, wenn repr() das Unicode-Zeichen ch maskieren sollte; andernfalls gibt sie 1 zurück. Zeichen, die maskiert werden sollten, sind in der Unicode-Zeichendatenbank definiert als
    • Cc (Other, Control)
    • Cf (Other, Format)
    • Cs (Other, Surrogate)
    • Co (Other, Private Use)
    • Cn (Other, Not Assigned)
    • Zl (Separator, Line), bezieht sich auf LINE SEPARATOR (‘\u2028’).
    • Zp (Separator, Paragraph), bezieht sich auf PARAGRAPH SEPARATOR (‘\u2029’).
    • Zs (Separator, Space) außer dem ASCII-Leerzeichen (‘\x20’). Zeichen dieser Kategorie sollten maskiert werden, um Mehrdeutigkeiten zu vermeiden.
  • Der Algorithmus zum Erstellen von repr()-Strings sollte geändert werden zu
    • Konvertiere CR, LF, TAB und ‘\’ zu ‘\r’, ‘\n’, ‘\t’, ‘\\’.
    • Konvertiere nicht druckbare ASCII-Zeichen (0x00-0x1f, 0x7f) zu ‘\xXX’.
    • Konvertiere führende Ersatzzeichenpaare ohne nachfolgendes Zeichen (0xd800-0xdbff, aber nicht gefolgt von 0xdc00-0xdfff) zu ‘\uXXXX’.
    • Konvertiere nicht druckbare Zeichen (Py_UNICODE_ISPRINTABLE() gibt 0 zurück) zu ‘\xXX’, ‘\uXXXX’ oder ‘\U00xxxxxx’.
    • Maskiere Anführungszeichen (Apostroph, 0x27) mit einem Backslash und füge ein Anführungszeichen am Anfang und Ende hinzu.
  • Setze den Unicode-Fehlerbehandlungsmechanismus für sys.stderr standardmäßig auf ‘backslashreplace’.
  • Füge eine neue Funktion zur Python C API hinzu PyObject *PyObject_ASCII (PyObject *o). Diese Funktion konvertiert jedes Python-Objekt mit PyObject_Repr() in einen String und maskiert dann alle Nicht-ASCII-Zeichen als Hexadezimalzahlen. PyObject_ASCII() erzeugt den gleichen String wie PyObject_Repr() in Python 2.
  • Füge eine neue eingebaute Funktion hinzu, ascii(). Diese Funktion konvertiert jedes Python-Objekt mit repr() in einen String und maskiert dann alle Nicht-ASCII-Zeichen als Hexadezimalzahlen. ascii() erzeugt den gleichen String wie repr() in Python 2.
  • Füge einen '%a' String-Formatierungsoperator hinzu. '%a' konvertiert jedes Python-Objekt mit repr() in einen String und maskiert dann alle Nicht-ASCII-Zeichen als Hexadezimalzahlen. Der '%a' Formatierungsoperator erzeugt den gleichen String wie '%r' in Python 2. Füge außerdem '!a' Konvertierungsflags zur string.format() Methode hinzu und '%A' Operator zu PyUnicode_FromFormat(). Sie konvertieren jedes Objekt in einen ASCII-String wie der '%a' String-Formatierungsoperator.
  • Füge eine isprintable()-Methode zum String-Typ hinzu. str.isprintable() gibt False zurück, wenn repr() ein Zeichen im String maskieren würde; andernfalls gibt es True zurück. Die isprintable()-Methode ruft intern die Py_UNICODE_ISPRINTABLE()-Funktion auf.

Begründung

Das repr() in Python 3000 sollte Unicode-basiert und nicht ASCII-basiert sein, genau wie die Strings von Python 3000. Außerdem sollte die Konvertierung nicht von der Locale-Einstellung beeinflusst werden, da die Locale nicht unbedingt mit der Locale des Ausgabegeräts übereinstimmen muss. Zum Beispiel ist es üblich, dass ein Daemon-Prozess in einer ASCII-Umgebung gestartet wird, aber UTF-8 in seine Log-Dateien schreibt. Auch Webanwendungen möchten möglicherweise Fehlerinformationen in einer lesbareren Form basierend auf der Kodierung der HTML-Seite melden.

Zeichen, die vom Benutzer-Terminal nicht unterstützt werden, können beim Drucken durch den Fehlerbehandler des Unicode-Encoders hex-maskiert werden. Wenn der Fehlerbehandler der Ausgabedatei ‘backslashreplace’ ist, werden solche Zeichen hex-maskiert, ohne eine UnicodeEncodeError auszulösen. Wenn beispielsweise die Standardkodierung ASCII ist, wird print('Hello ¢') als ‘Hello \xa2’ ausgegeben. Wenn die Kodierung ISO-8859-1 ist, wird ‘Hello ¢’ ausgegeben.

Der Standardfehlerbehandler für sys.stdout ist ‘strict’. Andere Anwendungen, die die Ausgabe lesen, verstehen möglicherweise keine hex-maskierten Zeichen, daher sollten nicht unterstützte Zeichen beim Schreiben abgefangen werden. Wenn nicht unterstützte Zeichen maskiert werden müssen, sollte der Fehlerbehandler explizit geändert werden. Im Gegensatz zu sys.stdout löst sys.stderr standardmäßig keine UnicodeEncodingError aus, da der Standardfehlerbehandler ‘backslashreplace’ ist. Das Drucken von Fehlermeldungen, die Nicht-ASCII-Zeichen enthalten, an sys.stderr löst also keine Ausnahme aus. Auch Informationen über nicht abgefangene Ausnahmen (Ausnahmeobjekt, Traceback) werden vom Interpreter ohne Ausnahmen ausgegeben.

Alternative Lösungen

Um das Debugging in nicht-lateinischen Sprachen ohne Änderung von repr() zu erleichtern, wurden andere Vorschläge gemacht.

  • Bereitstellung eines Werkzeugs zum Drucken von Listen oder Dictionaries.

    Zu debuggende Strings sind nicht nur in Listen oder Dictionaries enthalten, sondern auch in vielen anderen Objekttypen. Dateiobjekte enthalten einen Dateinamen in Unicode, Ausnahmeobjekte enthalten eine Nachricht in Unicode usw. Diese Strings sollten in lesbarer Form gedruckt werden, wenn sie repr()ed werden. Es ist unwahrscheinlich, dass ein Werkzeug zur Ausgabe aller möglichen Objekttypen implementiert werden kann.

  • Verwendung von sys.displayhook und sys.excepthook.

    Für interaktive Sitzungen können wir Hooks schreiben, um hex-maskierte Zeichen wieder in die ursprünglichen Zeichen umzuwandeln. Diese Hooks werden jedoch nur beim Drucken des Ergebnisses der Auswertung eines in einer interaktiven Python-Sitzung eingegebenen Ausdrucks aufgerufen und funktionieren nicht für die print()-Funktion, für nicht-interaktive Sitzungen oder für logging.debug("%r", ...) usw.

  • Unterklasse von sys.stdout und sys.stderr.

    Es ist schwierig, eine Unterklasse zu implementieren, um hex-maskierte Zeichen wiederherzustellen, da zu dem Zeitpunkt, an dem es sich um einen String handelt, nicht genügend Informationen vorhanden sind, um die Maskierung in allen Fällen korrekt rückgängig zu machen. Beispielsweise sollte print("\\"+"u0041") als ‘\u0041’ und nicht als ‘A’ ausgegeben werden. Aber es gibt keine Möglichkeit, Dateiobjekte zu unterscheiden.

  • Machen Sie die von unicode_repr() verwendete Kodierung einstellbar und behalten Sie das bestehende repr() als Standard bei.

    Mit einstellbarem repr() ist das Ergebnis der Verwendung von repr() unvorhersehbar und würde es unmöglich machen, korrekten Code zu schreiben, der repr() beinhaltet. Und wenn das aktuelle repr() der Standard ist, bleibt die alte Konvention bestehen und Benutzer erwarten möglicherweise ASCII-Strings als Ergebnis von repr(). Drittanbieter-Anwendungen oder Bibliotheken könnten verwirrt sein, wenn eine benutzerdefinierte repr()-Funktion verwendet wird.

Abwärtskompatibilität

Die Änderung von repr() kann bestehenden Code brechen, insbesondere Testcode. Fünf Regressionstests von Python schlagen mit dieser Änderung fehl. Wenn Sie repr()-Strings ohne Nicht-ASCII-Zeichen wie in Python 2 benötigen, können Sie die folgende Funktion verwenden.

def repr_ascii(obj):
    return str(repr(obj).encode("ASCII", "backslashreplace"), "ASCII")

Für die Protokollierung oder für das Debugging kann der folgende Code eine UnicodeEncodeError auslösen.

log = open("logfile", "w")
log.write(repr(data))     # UnicodeEncodeError will be raised
                          # if data contains unsupported characters.

Um Ausnahmen zu vermeiden, können Sie explizit den Fehlerbehandler angeben.

log = open("logfile", "w", errors="backslashreplace")
log.write(repr(data))  # Unsupported characters will be escaped.

Für eine Konsole, die eine Unicode-basierte Kodierung verwendet, z.B. en_US.utf8 oder de_DE.utf8, funktioniert der backslashreplace-Trick nicht und alle druckbaren Zeichen werden nicht maskiert. Dies führt zu Problemen mit ähnlich aussehenden Zeichen in westlichen, griechischen und kyrillischen Sprachen. Diese Sprachen verwenden ähnliche (aber unterschiedliche) Alphabete (die von einem gemeinsamen Vorfahren abstammen) und enthalten Buchstaben, die ähnlich aussehen, aber unterschiedliche Zeichencodes haben. Es ist beispielsweise schwer, lateinische ‘a’, ‘e’ und ‘o’ von kyrillischen ‘а’, ‘е’ und ‘о’ zu unterscheiden. (Die visuelle Darstellung hängt natürlich sehr von den verwendeten Schriftarten ab, aber normalerweise sind diese Buchstaben fast ununterscheidbar.) Um das Problem zu vermeiden, kann der Benutzer die Terminalkodierung anpassen, um ein für seine Umgebung geeignetes Ergebnis zu erzielen.

Abgelehnte Vorschläge

  • Füge encoding- und errors-Argumente zur eingebauten print()-Funktion hinzu, mit den Standardwerten sys.getfilesystemencoding() und ‘backslashreplace’.

    Kompliziert zu implementieren und generell keine gute Idee. [2]

  • Verwende Zeichennamen zur Maskierung von Zeichen anstelle von Hexadezimal-Zeichencodes. Zum Beispiel kann repr('\u03b1') zu "\N{GREEK SMALL LETTER ALPHA}" konvertiert werden.

    Die Verwendung von Zeichennamen kann im Vergleich zur Hex-Maskierung sehr umständlich sein. z.B. wird repr("\ufbf9") zu "\N{ARABIC LIGATURE UIGHUR KIRGHIZ YEH WITH HAMZA ABOVE WITH ALEF MAKSURA ISOLATED FORM}" konvertiert.

  • Der Standardfehlerbehandler für sys.stdout sollte ‘backslashreplace’ sein.

    An stdout geschriebene Inhalte könnten von einem anderen Programm konsumiert werden, das die \-Escapes falsch interpretiert. Für interaktive Sitzungen ist es möglich, den ‘backslashreplace’-Fehlerbehandler zum Standard zu machen, aber dies kann zu Verwirrung führen, wie z.B. „es funktioniert im interaktiven Modus, aber nicht beim Umleiten in eine Datei“.

Implementierung

Der Autor hat einen Patch in http://bugs.python.org/issue2630 geschrieben; dieser wurde am 06-11-2008 in der Python 3.0-Branch unter der Revision 64138 übernommen.

Referenzen


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

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