PEP 686: UTF-8-Modus in Python 3.15 standardmäßig aktiviert
(peps.python.org)- Python 3.15 aktiviert den UTF-8-Modus standardmäßig und setzt damit die Standardkodierung für Dateien, Standard-Ein-/Ausgabe und Pipes auf UTF-8
- UTF-8 wird bereits als De-facto-Standardkodierung für Quelldateien, JSON·TOML·YAML, wichtige Editoren, Webdaten sowie Node.js·Go·Rust·Java verwendet, was die Interoperabilität verbessert
- Die bisherige Standardkodierung unterschied sich je nach Plattform, sodass bei Unix-Entwicklern, die
encoding="utf-8"weglassen, auf Windows und anderen Systemen Mismatch-Bugs entstehen können - Bei Bedarf lässt sich das mit
PYTHONUTF8=0oder-X utf8=0deaktivieren; für Kompatibilitätsprüfungen kommenEncodingWarning,encoding="utf-8",encoding="locale"undlocale.getencoding()zum Einsatz - Programme, die sich auf die Standardkodierung verlassen, können vor allem unter Windows
UnicodeError, Mojibake oder stille Datenbeschädigung verursachen und sollten daher vorab geprüft werden
Die geänderte Standardkodierung in Python 3.15
- PEP 686 führt die Änderung ein, den UTF-8-Modus aus PEP 540 standardmäßig zu aktivieren
- Für Dateien, stdio und Pipes, bei denen eine Standardkodierung benötigt wird, verwendet Python konsistent UTF-8
- Ab Python 3.15 ist dies standardmäßig aktiv; Nutzer können es auf folgende Weise deaktivieren
PYTHONUTF8=0-X utf8=0
Warum UTF-8 zum Standard wird
- UTF-8 hat sich in vielen Umgebungen als Standard für Textkodierung etabliert
- Die Standardkodierung von Python-Quelldateien ist UTF-8
- JSON, TOML und YAML verwenden UTF-8
- Die meisten Texteditoren, darunter Visual Studio Code und Windows Notepad, verwenden standardmäßig UTF-8
- Die meisten Websites und Textdaten im Internet verwenden UTF-8
- Viele populäre Programmiersprachen, darunter Node.js, Go, Rust und Java, verwenden standardmäßig UTF-8
- Wenn Pythons Standardkodierung auf UTF-8 umgestellt wird, verbessert das die Interoperabilität mit anderen Tools, Sprachen und Datenformaten
- Viele Python-Entwickler in Unix-Umgebungen vergessen, dass die Standardkodierung plattformabhängig ist, und lassen beim Lesen von UTF-8-Texten wie JSON-, TOML-, Markdown- oder Python-Dateien
encoding="utf-8"weg - Unterschiede bei den Standardkodierungen je nach Plattform werden dadurch zu einer Bug-Ursache, wenn solcher Code in anderen Umgebungen ausgeführt wird
Anpassungen an der locale-API und encoding="locale"
- Da der UTF-8-Modus
locale.getpreferredencoding(False)beeinflusst, wird eine API benötigt, die die locale-Kodierung unabhängig vom UTF-8-Modus liefert locale.getencoding()wurde zu diesem Zweck ergänzt und gibt die locale-Kodierung zurück, ignoriert dabei jedoch den UTF-8-Modus- Diese API wurde in Python 3.11 hinzugefügt
- Wenn die Option
warn_default_encodinggesetzt ist, erzeugtlocale.getpreferredencoding()wieopen()einEncodingWarningaus PEP 597 - PEP 597 hat für
TextIOWrapperdie Optionencoding="locale"ergänzt, damit die locale-Kodierung explizit angegeben werden kann - Zuvor verwendete
TextIOWrapperim UTF-8-Modus selbst bei Angabe vonencoding="locale""UTF-8"- Das entsprach nicht der Motivation von PEP 597
- Der Fall, dass der UTF-8-Modus standardmäßig aktiv wird, wenn sich Pythons Standard-Textkodierung ändert, war damals nicht vorhergesehen worden
- Diese Inkonsistenz wurde in Python 3.11 behoben; auch im UTF-8-Modus wird bei Übergabe von
encoding="locale"die locale-Kodierung verwendet
Abwärtskompatibilität und Migrationsschritte
- Da die meisten Unix-Systeme eine UTF-8-locale verwenden und Python den UTF-8-Modus aktiviert, wenn die locale C oder POSIX ist, betrifft die Änderung vor allem Windows-Nutzer
- Python-Programme, die von der Standardkodierung abhängen, können folgende Probleme verursachen
UnicodeError- Mojibake
- stille Datenbeschädigung
- Das empfohlene Vorgehen zur Behebung von Abwärtskompatibilitätsproblemen ist wie folgt
- Den UTF-8-Modus deaktivieren
- Mit
EncodingWarningaus PEP 597 die Stellen finden, die vom UTF-8-Modus beeinflusst werden- Wenn die Option
encodingweggelassen wurde, den Einsatz vonencoding="utf-8"oderencoding="locale"prüfen - Wenn
locale.getpreferredencoding()verwendet wurde, den Einsatz von"utf-8"oderlocale.getencoding()prüfen
- Wenn die Option
- Die Anwendung im UTF-8-Modus testen
Vorbilder aus Ruby und Java sowie abgelehnte Alternativen
- Ruby hat in Ruby 3.0 im Jahr 2020 die Standard-
external_encodingunter Windows auf UTF-8 umgestellt - Java hat in JDK 18 im Jahr 2022 die Standard-Textkodierung auf UTF-8 umgestellt
- Sowohl Ruby als auch Java bieten Optionen für Abwärtskompatibilität, aber keine Warnung zur Nutzung der Standardkodierung wie Pythons
EncodingWarning - Die Abschaffung der Nutzung einer Standardkodierung insgesamt wurde abgelehnt
- Häufig wird die Standardkodierung genutzt, um nur ASCII-Text zu lesen oder zu schreiben
- Für Anwendungen, die nur unter Unix laufen und nicht plattformübergreifend sind, wäre eine solche Warnung nicht nützlich
- Wenn
encodingüberall erzwungen würde, wäre die Belastung für Nutzer groß, und vieleDeprecationWarningkönnten dazu führen, dass Warnungen generell ignoriert werden - PEP 387 verlangt zwar Warnungen bei Änderungen, die die Abwärtskompatibilität brechen, aber nicht zwingend
DeprecationWarning
- Auch die Idee,
PYTHONIOENCODINGals Standardkodierung für Pipes im Modulsubprocesszu verwenden, wurde abgelehnt- Dadurch wäre es möglich, selbst im UTF-8-Modus für
subprocess.Popen(text=True)eine Legacy-Kodierung zu verwenden - Das würde jedoch die „Standardkodierung“ unnötig kompliziert machen, und auch dieser Ansatz wäre selbst eine Änderung mit Auswirkungen auf die Abwärtskompatibilität
- Nutzer können den UTF-8-Modus deaktivieren, bis sie
text=Truedurchencoding="utf-8"oderencoding="locale"ersetzt haben
- Dadurch wäre es möglich, selbst im UTF-8-Modus für
Perspektive der Nutzerbildung
- Neue Nutzer müssen im ersten Jahr seltener etwas über Textkodierungen lernen
- Erst beim Umgang mit Textdateien, die nicht UTF-8 verwenden, müssen sie sich mit Kodierungen beschäftigen
- Bestehende Nutzer sollten die betroffenen Stellen gemäß dem Kompatibilitätsverfahren prüfen
1 Kommentare
Meinungen auf Hacker News
Dass die Standardkodierung für Textdateien je nach Plattform unterschiedlich ist, war schon immer nervig; diese Änderung ist daher willkommen.
Gut ist auch, dass man nicht versucht hat, gleich die Dateisystem-Kodierung anzufassen. Das ist ein eigenes Problem und auf seine Weise mühsam.
Es war ein großer Fehler, dass Windows lange keine einfache Möglichkeit bot, ANSI-Funktionen wie
TextOutAdie UTF-8-Codepage verwenden zu lassen. Dass es per Manifest-Datei möglich wurde, kam erst irgendwann mitten in der Entwicklung von Windows 10; so eine Funktion hätte schon zu Zeiten von NT4 oder Windows 98 vorhanden sein müssen.Es hing nicht nur von der Plattform ab, sondern auch von der bevorzugten Locale des Nutzers, und die C-Standardbibliothek verhielt sich genauso. Unter Unix/Linux war in westeuropäischen Sprachen zum Beispiel
iso-8859-1verbreitet; nach Einführung des Euro wechselte man oft zuiso-8859-15mit dem Zeichen€. Dass UTF-8 problemlos zu funktionieren begann, war etwa Ende der 2000er der Fall, und Debian stellte mit dem Etch-Release den Standard auf UTF-8 um.Auf dem Firmenlaptop liefen alle lokalen Tests, aber nach dem Deployment auf einen Linux-Host konnte eine nachgelagerte Anwendung die Daten nicht verarbeiten, weil sie CRLF verlangte. Eines dieser kleinen, dummen Probleme, an die man gelegentlich denken muss. Allerdings ist es auch eine berechtigte Frage, warum neu geschriebene Software einen bestimmten Zeilenabschluss verlangt.
Es ist gut, sich nicht auf instabile Systemstandards zu verlassen.
Solche Werte neigen dazu, irgendwann anders zurückzukommen, als man angenommen hatte. Vor ein paar Jahren, als ich mit Ubuntu und
init.d-Skripten zu tun hatte, lief ein Skript, das Java startete, als root – und das war noch vor Docker, also umso mehr –, in einer Shell, die für normale Nutzer keine vernünftigen UTF-8-Defaults setzte. Dadurch wurde eine schlechte Verwendung einer Java-API sichtbar, die den OS-Standard nutzte.Heutzutage gibt es für die meisten Dinge API-Varianten, bei denen man die Kodierung explizit angeben kann, und statische Code-Analyzer warnen, wenn man die falsche verwendet. Aber wenn nur eine Stelle fehlt, beginnt der Inhalt kaputtzugehen. Inzwischen ist die Verwendung einer Nicht-UTF-8-Kodierung sehr wahrscheinlich meist unbeabsichtigt; wenn sie beabsichtigt ist, sollte man sie explizit angeben, statt sich auf merkwürdige indirekte OS-Einstellungen zu verlassen. Daher ist das eine gute Änderung, und Code, der dadurch bricht, sollte lieber mit einer einfachen Korrektur versehen werden.
.gitignoreverwendet, die von einer als Alias angelegtentouch-Funktion in PowerShell erzeugt wurde, und Git wollte sie einfach nicht beachten.Beim Nachsehen stellte sich heraus, dass die erzeugte Textdatei UTF-16 war und deshalb praktisch ignoriert wurde. Daraus habe ich gelernt und den Systemstandard auf UTF-8 umgestellt, aber inzwischen verlasse ich mich einfach auf den Texteditor.
Wenn
printf("%f", 4.2)je nach Umgebung auf magische Weise andere Strings ausgibt, verursacht das mehr Probleme, als es löst. Wenn man Locale-abhängiges Verhalten will, sollte man die Locale-Informationen oder die relevanten Teile explizit an die Funktion übergeben.Es gibt eine Heuristik, die in den letzten Jahrzehnten immer zutreffender geworden ist: Wenn irgendwo eine
charset-Einstellung steht und sie nicht UTF-8 ist, ist sie falsch.Python 2 war gegenüber Zeichensätzen indifferent und funktionierte daher immer, aber die Verbesserungen in Python 3 waren nicht einfach nur Verbesserungen. So erkennt man ein Python-3-Skript im Unterschied zu einem Python-2-Skript: Wenn der String
utf-8darin vorkommt, ist es Python 3, und wenn es nur in einerC.UTF-8-Locale läuft, ist es Python 3. Diese Änderung wirkt wie eine „Reparatur“ von Python 3 und ist daher willkommen.Ich dachte, das sei seit Python 3 der Standard.
u""in Python 3 unnötig wurde.Ich habe gerade in Python 2.7
"éķů"eingegeben, und es gab die UTF-8-Bytes dieser Zeichen aus; daher bin ich mir nicht ganz sicher, was dasu-Präfix genau bewirkte. Aber eine der großen Änderungen beim Wechsel von Python 2 zu 3 war, dass Strings eine Kodierung haben und Byte-Strings zu Byte-Sequenzen ohne Kodierung wurden. Bei dieser Änderung scheint es vor allem um Umgebungen wie Windows zu gehen, in denen die Standardkodierung nicht UTF-8 ist und man beiopen('filename', mode='r')explizitopen('filename', mode='r', encoding='UTF-8')angeben musste.Wie bei
Path("filenames use their own encoding").write_text("file content encoding uses yet another encoding")sind die Kodierungen für String-Literale, Dateinamen und Dateiinhalte jeweils unterschiedlich. Die entsprechenden Kodierungen sind UTF-8 beitokenize.open,sys.getfilesystemencoding()vonos.fsencodeundlocale.getpreferredencoding()beiopen.„Auch andere beliebte Programmiersprachen, darunter Node.js, Go, Rust und Java, verwenden standardmäßig UTF-8“ – da habe ich wohl verpasst, dass Java von UTF-16 zu UTF-8 gewechselt ist.
Intern verwendet die Klasse
Stringweiterhin UTF-16- und latin-1-Kodierungen, und die JVM nutzt wie früher eine modifizierte UTF-8-Kodierung. Die KlasseStringverwendete ursprünglich nur UTF-16, nutzt aber seit Java 9, wenn möglich, auch eine latin-1-Kodierung mit 1 Byte pro Zeichen.Java hat nie UTF-16 als Standard für die Lese-/Schreibkodierung verwendet.
Ist die interne Kodierung von CPython jetzt UTF-8?
Python-Strings lassen sich per Index adressieren, aber Random Access ist selten genug, dass Lazy Indexing bei Bedarf wohl in Ordnung wäre. Wenn man nur ein Zeichen vor- oder zurückgeht, braucht man keinen Index; daher wäre eine interne Darstellung als UTF-8 durchaus möglich.
strerfolgt durch einPyUnicode-Objekt.Wenn UTF-8-Bytes angefordert werden, wird bei Bedarf ein
bytes-Objekt erzeugt, als Teil vonPyUnicodezwischengespeichert und zusammen mitPyUnicodefreigegeben. Separat werden die Codepoints, aus denen der String besteht, in einem einfachen Array gespeichert, damit Random Access möglich ist. Jeder Codepoint kann 1, 2 oder 4 Byte groß sein; wenn beim Erzeugen vonPyUnicodeder maximale Codepoint-Wert angegeben wird, wird er auf einen der Werte 127, 255, 65535 oder 1.114.111 aufgerundet, wodurch entschieden wird, ob 1/2/4 Byte verwendet werden.Wenn der maximale Codepoint-Wert 127 ist, kann diese Array-Darstellung direkt als UTF-8 verwendet werden. Die Antwort auf die Frage lautet also: Viele Strings, deren Codepoints alle höchstens 127 sind, werden als UTF-8 gespeichert. Allerdings sollte man beim Iterieren über Strings nicht in Codepoints denken. Ein vom Nutzer wahrgenommenes Zeichen, also ein Graphem-Cluster, besteht aus einem oder mehreren Codepoints. Ein e mit Akzent kann zum Beispiel aus dem Codepoint
egefolgt von einem kombinierenden Akzent-Codepoint bestehen, und das Phönix-Emoji setzt sich aus einem Vogel-Emoji, einem Zero-Width-Joiner und einem Feuer-Emoji zusammen. Auch einige Schriftsysteme, die von Hunderten Millionen Menschen verwendet werden, funktionieren ähnlich: An Konsonanten werden kombinierende Zeichen für Vokale angehängt. Dieses- -besteht aus 5 Codepoints, und es gibt einen guten Artikel darüber, wie verschiedene Sprachen dessen „Länge“ melden: https://hsivonen.fi/string-length/. Das stammt aus der Erfahrung, gerade Unicode TR29, das diesen Bereich behandelt, als Python-C-Erweiterung implementiert zu haben.Ich frage mich, warum nicht
utf-8-sig. Das würde eine optionale BOM behandeln; erst letzte Woche musste ich wegen genau so etwas ein Skript anpassen.Es wird auch nicht empfohlen, und ich finde es heutzutage vertretbar, an einer BOM zu scheitern.
Wenn es um UTF-8 geht: Der Linux-Framebuffer hätte schon vor langer Zeit ordentliche UTF-8-Unterstützung bekommen müssen.
Ich meine echte Unterstützung, nicht nur 256/512 Glyphen. Sogar GNU Hurd hatte etwa seit 2007 eine bessere Terminal-Konsole mit UTF-8-Unterstützung, und jetzt haben wir 2024.
Gut. Jetzt bleibt nur noch, dass JS auf UTF-8 umstellt.
Natürlich kann JS nicht verbessert werden. Anders als jede andere Programmiersprache muss es ja mit Code kompatibel bleiben, der 1995 geschrieben wurde.
Die interne Darstellung von Strings ist ein anderes Thema, und genau wie JavaScript verwendet auch Python intern nicht „einfach UTF-8“.
Zu „Viele Python-Entwickler, die Unix verwenden, vergessen, dass die Standardkodierung plattformabhängig ist, und lassen
encoding="utf-8"weg, wenn sie UTF-8-kodierte Textdateien lesen“: Vielleicht haben sie es nicht vergessen, sondern es ist einfach nicht bekannt genug.Ehrlich gesagt dachte ich, Python verwende überall ausschließlich UTF-8, sofern man nicht ausdrücklich etwas anderes verlangt.
bytes.decodeundstr.encodeverwenden zumindest seit Python 3 standardmäßig UTF-8. Beim Dekodieren von Dateinamen dagegen wird standardmäßigsys.getfilesystemencoding()verwendet; unter Windows und macOS ist das ebenfalls UTF-8, unter Linux hängt es jedoch vom Locale ab, genauer vonCODESET. Und schließlich verwendetopendirektlocale.getencoding().