Python: UnicodeEncodeError beim Schreiben und Ausgeben
UnicodeEncodeError in Python: Umlaute oder Emojis passen nicht in die Zielkodierung. So legst du sie für Dateien und Konsole ausdrücklich fest.
Aufwand:
ca. 15 Min.
Voraussetzung:
Python 3, Editor, Terminal oder Eingabeaufforderung
Gilt für:
Python 3 (alle aktuellen Versionen)
Das Symptom
Das Skript rechnet fehlerfrei, sammelt brav seine Daten – und stirbt in der
letzten Zeile, ausgerechnet beim Hinausschreiben:
UnicodeEncodeError: 'charmap' codec can't encode character '€' in position 12: character maps to <undefined>
Mal trifft es f.write(...), mal ein simples print() auf der Windows-Konsole.
Auf dem eigenen Rechner lief dasselbe Skript gestern noch durch.
Die „Layer 8“-Wahrheit
Zwei Fehler mit fast gleichem Namen, und trotzdem sind es zwei verschiedene
Baustellen. Beim UnicodeDecodeError kommen Bytes herein, die nicht zur
angenommenen Kodierung passen – ein Lesefehler, die Datei ist heil, die Annahme
war falsch. Beim UnicodeEncodeError geht es genau andersherum: Dein Text ist
völlig in Ordnung, er soll nur in eine Kodierung gepresst werden, die das
betreffende Zeichen nicht kennt. Decode am Eingang, Encode am Ausgang. Wer diese
Richtung einmal sauber im Kopf hat, sucht nie wieder an der falschen Stelle.
Der eigentliche Ärger entsteht dadurch, dass Python die Kodierung stillschweigend
von der Umgebung erbt. Die Dokumentation zu open() sagt es ohne Umschweife: Der
Standard ist plattformabhängig, nämlich das, was locale.getencoding()
zurückgibt. Auf dem Entwicklungsrechner ist das UTF-8, auf dem Zielsystem
vielleicht eine ältere Codepage, in der es kein Euro-Zeichen und ganz sicher kein
Emoji gibt. Derselbe Code, dieselben Daten, unterschiedlicher Ausgang – der
Klassiker hinter dem Satz „bei mir lief es doch“.
Die Reparatur ist deshalb keine Fehlerbehandlung, sondern eine Entscheidung:
Kodierung hinschreiben, statt sie sich vom Betriebssystem zuflüstern zu lassen.
Für Dateien ist das encoding='utf-8' beim Öffnen, für die Konsole
PYTHONIOENCODING, für alles auf einmal der UTF-8-Modus über -X utf8
beziehungsweise PYTHONUTF8=1. Und die verlockende Abkürzung errors='ignore'
ist keine Lösung, sondern ein Trick, mit dem man sich das Problem aus dem
Blickfeld schafft: Die Dokumentation warnt ausdrücklich, dass das Ignorieren von
Kodierfehlern zu Datenverlust führen kann. Das Programm läuft dann durch, die
Zeichen sind trotzdem weg – nur beschwert sich niemand mehr darüber.
Die echte, verifizierte Lösung
1000 % faktenbasiert. Jeder Befehl ist real, jeder Pfad existiert. Schritt für Schritt:
1.
Die Richtung des Fehlers feststellen
Steht im Traceback UnicodeEncodeError, hast du geschrieben oder ausgegeben – nicht gelesen. Die Meldung nennt den überforderten Codec, das Zeichen und seine Position. Das ist bereits die halbe Diagnose: Der Text ist in Ordnung, die Zielkodierung kann ihn nur nicht darstellen. Welche Kodierung dein Ausgabekanal gerade benutzt, verrät dieser Einzeiler. Für den Fehler in die andere Richtung gibt es eine eigene Seite in dieser Sammlung.
Beim Öffnen einer Datei die Kodierung ausdrücklich angeben
Ohne encoding-Angabe nimmt Python laut Dokumentation eine plattformabhängige Vorgabe, nämlich die des Locale. Wer die Kodierung hinschreibt, macht das Programm auf jedem Rechner gleich – und UTF-8 kann jedes Zeichen darstellen, das du im Text haben kannst.
with open('bericht.txt', 'w', encoding='utf-8') as f:
f.write('Grüße aus Köln 🎉')
3.
Für die Standardausgabe PYTHONIOENCODING setzen
Bricht nicht das Schreiben in eine Datei ab, sondern ein print auf die Konsole, liegt es am Kanal, nicht an deinem Code. Diese Umgebungsvariable überschreibt laut Dokumentation die Kodierung für stdin, stdout und stderr, in der Form encodingname:errorhandler. Für stderr wird der Fehlerbehandler dabei ignoriert – dort gilt immer backslashreplace.
Den UTF-8-Modus einschalten, statt jede Stelle einzeln zu reparieren
Python kennt einen eigenen UTF-8-Modus. Die Dokumentation nennt zwei Wege, ihn zu aktivieren: die Option -X utf8 beim Aufruf oder die Umgebungsvariable PYTHONUTF8 mit dem Wert 1. Mit -X utf8=0 beziehungsweise PYTHONUTF8=0 wird er ausdrücklich abgeschaltet. Das ist der große Hebel für Altbestand, den man nicht Zeile für Zeile anfassen will.
python3 -X utf8 skript.py
# dauerhaft für die Sitzung (Windows, Eingabeaufforderung)
set PYTHONUTF8=1
5.
errors= nur bewusst und mit Nebenwirkung im Blick wählen
Der Standard ist strict und bricht ab – das ist gut so, denn es zeigt dir das Problem. Die anderen Handler sind Notlösungen mit Preisschild: ignore lässt Zeichen ersatzlos verschwinden (die Dokumentation warnt hier ausdrücklich vor Datenverlust), replace setzt ein Ersatzzeichen, backslashreplace schreibt Escape-Sequenzen, xmlcharrefreplace und namereplace sind laut Dokumentation nur beim Schreiben unterstützt. Keiner davon macht das Zeichen in der Zieldatei wieder lesbar.
# läuft durch, verändert aber die Ausgabe - bewusst einsetzen
open('bericht.txt', 'w', encoding='cp1252', errors='backslashreplace')
6.
Prüfen, ob das Ziel die Zeichen überhaupt kann
Wenn eine fremde Schnittstelle zwingend eine alte Codepage verlangt, ist der Fehler kein Bug, sondern eine Aussage: Das Zeichen existiert dort nicht. Dann gehört die Entscheidung nach vorn – Zeichen vorher ersetzen (etwa das Euro-Zeichen durch EUR), statt sie hinten still verschwinden zu lassen. Ein kurzer Test zeigt, was die Zielkodierung trägt.
Die Richtung. Decodieren heißt: Bytes von der Platte werden zu Text – das passiert beim Lesen, und dort scheitert es, wenn die Bytes nicht zur angenommenen Kodierung passen. Encodieren heißt: Text im Speicher wird zu Bytes – das passiert beim Schreiben in eine Datei oder beim Ausgeben auf die Konsole, und dort scheitert es, wenn das Zeichen in der Zielkodierung schlicht nicht existiert. Merksatz: Decode am Eingang, Encode am Ausgang. Beim Decode-Fehler ist die Datei in Ordnung und deine Annahme falsch; beim Encode-Fehler ist dein Text in Ordnung und das Ziel zu eng. Ein cp1252-Ausgabekanal kennt kein Emoji – da hilft keine Fehlerbehandlung, sondern nur eine Kodierung, die das Zeichen darstellen kann.
?Warum knallt es nur auf Windows oder nur im Terminal des Kollegen?
Weil Python die Kodierung nicht erfindet, sondern von der Umgebung übernimmt. Die Dokumentation zu open() sagt es unmissverständlich: Wird encoding nicht angegeben, ist die verwendete Kodierung plattformabhängig – nämlich das, was locale.getencoding() zurückgibt. Auf dem einen Rechner ist das UTF-8 und alles läuft, auf dem anderen eine ältere Codepage, in der ein einzelnes Sonderzeichen nicht vorkommt. Derselbe Code, dieselben Daten, unterschiedliches Ergebnis. Genau deshalb gehört die Kodierung in den Code und nicht in den Zufall der Umgebung.
?Kann ich nicht einfach errors='ignore' setzen und Ruhe haben?
Ruhe ja, Daten nein. Die Python-Dokumentation vermerkt zu diesem Handler ausdrücklich, dass das Ignorieren von Kodierfehlern zu Datenverlust führen kann – die betroffenen Zeichen verschwinden ersatzlos, und niemand sieht es der Ausgabedatei später an. Wenn ein Schreibvorgang gar nicht scheitern darf, ist backslashreplace die ehrlichere Wahl: Das Zeichen bleibt als Escape-Sequenz sichtbar und damit rekonstruierbar. Für Dateien, die weiterverarbeitet werden, ist aber ohnehin UTF-8 die richtige Antwort und keine Fehlerbehandlung.
Belege
Diese Seite ist gegen die folgenden Primärquellen geprüft – Hersteller, Standard oder Gesetzestext. Wer es nicht glaubt, muss es nicht: Da steht, wo es nachzulesen ist.
Zwei Namen sehen identisch aus, der Vergleich sagt trotzdem ungleich. Schuld ist nicht der Code, sondern die Unicode-Normalisierung: NFC gegen NFD.
SWLästigMixed Content: The page at … was loaded over HTTPS, but requested an insecure … This request has been blocked; the content must be served over HTTPS.