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

Python Enhancement Proposals

PEP 597 – Hinzufügen einer optionalen EncodingWarning

Autor:
Inada Naoki <songofacandy at gmail.com>
Status:
Final
Typ:
Standards Track
Erstellt:
05-Juni-2019
Python-Version:
3.10

Inhaltsverzeichnis

Zusammenfassung

Fügt eine neue Warnungskategorie EncodingWarning hinzu. Diese wird ausgegeben, wenn das encoding Argument für open() weggelassen wird und die standardmäßige, lokalspezifische Kodierung verwendet wird.

Die Warnung ist standardmäßig deaktiviert. Eine neue Befehlszeilenoption -X warn_default_encoding und eine neue Umgebungsvariable PYTHONWARNDEFAULTENCODING können verwendet werden, um sie zu aktivieren.

Ein Argumentwert "locale" für encoding wird ebenfalls hinzugefügt. Er gibt explizit an, dass die Locale-Kodierung verwendet werden soll, und unterdrückt die Warnung.

Motivation

Die Verwendung der Standardkodierung ist ein häufiger Fehler

Entwickler, die macOS oder Linux verwenden, vergessen möglicherweise, dass die Standardkodierung nicht immer UTF-8 ist.

Zum Beispiel ist die Verwendung von long_description = open("README.md").read() in setup.py ein häufiger Fehler. Viele Windows-Benutzer können solche Pakete nicht installieren, wenn in ihrer UTF-8-kodierten README.md-Datei mindestens ein Nicht-ASCII-Zeichen vorhanden ist (z. B. Emojis, Autorennamen, Copyright-Symbole usw.).

Von den 4000 am häufigsten heruntergeladenen Paketen von PyPI enthalten 489 Nicht-ASCII-Zeichen in ihrer README-Datei, und 82 schlagen fehl, wenn sie aus dem Quellcode in Nicht-UTF-8-Locales installiert werden, da für eine Nicht-ASCII-Datei keine Kodierung angegeben wurde. [1]

Ein weiteres Beispiel ist logging.basicConfig(filename="log.txt"). Einige Benutzer erwarten möglicherweise, dass es standardmäßig UTF-8 verwendet, aber tatsächlich wird die Locale-Kodierung verwendet. [2]

Selbst Python-Experten gehen möglicherweise davon aus, dass die Standardkodierung UTF-8 ist. Dies führt zu Fehlern, die nur unter Windows auftreten; siehe zum Beispiel [3], [4], [5] und [6].

Das Ausgeben einer Warnung, wenn das encoding-Argument weggelassen wird, hilft dabei, solche Fehler zu finden.

Expliziter Weg zur Verwendung der lokalspezifischen Kodierung

open(filename) gibt nicht explizit an, welche Kodierung erwartet wird

  • Wenn ASCII angenommen wird, ist dies kein Fehler, kann aber zu Leistungseinbußen unter Windows führen, insbesondere bei lokalspezifischen Kodierungen, die nicht Latin-1 sind.
  • Wenn UTF-8 angenommen wird, kann dies ein Fehler oder ein plattformspezifisches Skript sein.
  • Wenn die Locale-Kodierung angenommen wird, ist das Verhalten wie erwartet (kann sich jedoch ändern, wenn zukünftige Python-Versionen die Standardeinstellung ändern).

Aus dieser Sicht ist open(filename) kein lesbarer Code.

encoding=locale.getpreferredencoding(False) kann verwendet werden, um die Locale-Kodierung explizit anzugeben, ist aber zu lang und leicht misszuverstehen (man kann zum Beispiel vergessen, False als Argument zu übergeben).

Dieses PEP bietet eine explizite Möglichkeit, die Locale-Kodierung anzugeben.

Vorbereitung auf die Änderung der Standardkodierung zu UTF-8

Da UTF-8 zum De-facto-Standard für Textkodierung geworden ist, könnten wir in Zukunft standardmäßig dazu übergehen.

Eine solche Änderung würde jedoch viele Anwendungen und Bibliotheken beeinträchtigen. Wenn wir überall, wo das encoding-Argument weggelassen wird, DeprecationWarning ausgeben würden, wäre dies zu laut und schmerzhaft.

Obwohl dieses PEP keine Änderung der Standardkodierung vorschlägt, wird es helfen, diese Änderung zu ermöglichen, indem

  • die Anzahl der weggelassenen encoding-Argumente in Bibliotheken reduziert wird, bevor wir standardmäßig eine DeprecationWarning ausgeben.
  • Benutzern ermöglicht wird, encoding="locale" zu übergeben, um die aktuelle Warnung und jede zukünftig hinzugefügte DeprecationWarning zu unterdrücken und gleichzeitig ein konsistentes Verhalten beizubehalten, wenn spätere Python-Versionen die Standardeinstellung ändern, was die Unterstützung für jede Python-Version >=3.10 gewährleistet.

Spezifikation

EncodingWarning

Fügt eine neue EncodingWarning-Warnungsklasse als Unterklasse von Warning hinzu. Sie wird ausgegeben, wenn das encoding-Argument weggelassen wird und die standardmäßige, lokalspezifische Kodierung verwendet wird.

Optionen zur Aktivierung der Warnung

Die Option -X warn_default_encoding und die Umgebungsvariable PYTHONWARNDEFAULTENCODING werden hinzugefügt. Sie werden verwendet, um EncodingWarning zu aktivieren.

sys.flags.warn_default_encoding wird ebenfalls hinzugefügt. Das Flag ist wahr, wenn EncodingWarning aktiviert ist.

Wenn das Flag gesetzt ist, geben io.TextIOWrapper(), open() und andere Module, die sie verwenden, eine EncodingWarning aus, wenn das encoding-Argument weggelassen wird.

Da EncodingWarning eine Unterklasse von Warning ist, werden sie standardmäßig angezeigt (wenn das Flag warn_default_encoding gesetzt ist), im Gegensatz zu DeprecationWarning.

encoding="locale"

io.TextIOWrapper akzeptiert "locale" als gültiges Argument für encoding. Es hat dieselbe Bedeutung wie das aktuelle encoding=None, außer dass io.TextIOWrapper keine EncodingWarning ausgibt, wenn encoding="locale" angegeben ist.

io.text_encoding()

io.text_encoding() ist ein Helfer für Funktionen mit einem encoding=None-Parameter, die ihn an io.TextIOWrapper() oder open() übergeben.

Eine reine Python-Implementierung würde so aussehen

def text_encoding(encoding, stacklevel=1):
    """A helper function to choose the text encoding.

    When *encoding* is not None, just return it.
    Otherwise, return the default text encoding (i.e. "locale").

    This function emits an EncodingWarning if *encoding* is None and
    sys.flags.warn_default_encoding is true.

    This function can be used in APIs with an encoding=None parameter
    that pass it to TextIOWrapper or open.
    However, please consider using encoding="utf-8" for new APIs.
    """
    if encoding is None:
        if sys.flags.warn_default_encoding:
            import warnings
            warnings.warn(
                "'encoding' argument not specified.",
                EncodingWarning, stacklevel + 2)
        encoding = "locale"
    return encoding

Zum Beispiel kann pathlib.Path.read_text() dies so verwenden

def read_text(self, encoding=None, errors=None):
    encoding = io.text_encoding(encoding)
    with self.open(mode='r', encoding=encoding, errors=errors) as f:
        return f.read()

Durch die Verwendung von io.text_encoding() wird EncodingWarning für den Aufrufer von read_text() ausgegeben, anstatt für read_text() selbst.

Betroffene Standardbibliotheksmodule

Viele Standardbibliotheksmodule werden von dieser Änderung betroffen sein.

Die meisten APIs, die encoding=None akzeptieren, verwenden io.text_encoding(), wie im vorherigen Abschnitt beschrieben.

Wo die Verwendung der Locale-Kodierung als Standardkodierung sinnvoll ist, wird stattdessen encoding="locale" verwendet. Zum Beispiel wird das Modul subprocess die Locale-Kodierung als Standard für Pipes verwenden.

Viele Tests verwenden open() ohne Angabe von encoding, um ASCII-Textdateien zu lesen. Sie sollten mit encoding="ascii" neu geschrieben werden.

Begründung

Opt-in-Warnung

Obwohl DeprecationWarning standardmäßig unterdrückt wird, wäre die ständige Ausgabe von DeprecationWarning, wenn das encoding-Argument weggelassen wird, zu laut.

Laute Warnungen können dazu führen, dass Entwickler die DeprecationWarning ignorieren.

"locale" ist kein Codec-Alias

Wir fügen "locale" nicht als Codec-Alias hinzu, da die Locale zur Laufzeit geändert werden kann.

Zusätzlich prüft TextIOWrapper os.device_encoding(), wenn encoding=None ist. Dieses Verhalten kann nicht in einem Codec implementiert werden.

Abwärtskompatibilität

Die neue Warnung wird nicht standardmäßig ausgegeben, daher ist dieses PEP zu 100 % abwärtskompatibel.

Vorwärtskompatibilität

Das Übergeben von "locale" als Argument für encoding ist nicht zukunftssicher. Code, der es verwendet, funktioniert nicht auf Python-Versionen älter als 3.10 und löst stattdessen LookupError: unknown encoding: locale aus.

Bis Entwickler die Unterstützung für Python 3.9 fallen lassen können, kann EncodingWarning nur zum Finden fehlender encoding="utf-8"-Argumente verwendet werden.

Wie man das lehrt

Für neue Benutzer

Da EncodingWarning zum Schreiben plattformübergreifenden Codes verwendet wird, gibt es keinen Bedarf, neue Benutzer darauf zu schulen.

Wir können einfach empfehlen, UTF-8 für Textdateien zu verwenden und encoding="utf-8" beim Öffnen zu verwenden.

Für erfahrene Benutzer

Die Verwendung von open(filename) zum Lesen von Textdateien, die in UTF-8 kodiert sind, ist ein häufiger Fehler. Es funktioniert möglicherweise nicht unter Windows, da UTF-8 nicht die Standardkodierung ist.

Sie können -X warn_default_encoding oder PYTHONWARNDEFAULTENCODING=1 verwenden, um diese Art von Fehler zu finden.

Das Weglassen des encoding-Arguments ist kein Fehler beim Öffnen von Textdateien, die in der Locale-Kodierung kodiert sind, aber encoding="locale" wird in Python 3.10 und neuer empfohlen, da es expliziter ist.

Referenzimplementierung

https://github.com/python/cpython/pull/19481

Diskussionen

Der neueste Diskussionsfaden ist: https://mail.python.org/archives/list/python-dev@python.org/thread/SFYUP2TWD5JZ5KDLVSTZ44GWKVY4YNCV/

  • Warum dies nicht in Linters implementieren?
    • encoding="locale" und io.text_encoding() müssen in Python implementiert werden.
    • Es ist schwierig, alle Aufrufer von Funktionen zu finden, die open() oder TextIOWrapper() wrappen (siehe Abschnitt io.text_encoding()).
  • Viele Entwickler werden die Option nicht verwenden.
    • Einige werden es tun und die Warnungen an die Bibliotheken melden, die sie verwenden, sodass die Option es wert ist, auch wenn viele Entwickler sie nicht aktivieren.
    • Zum Beispiel habe ich [7] und [8] gefunden, indem ich pip install -U pip ausgeführt habe, und [9], indem ich tox mit der Referenzimplementierung ausgeführt habe. Dies zeigt, wie diese Option verwendet werden kann, um potenzielle Probleme zu finden.

Referenzen


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

Zuletzt geändert: 2025-02-01 08:56:52 GMT