Von Python, Umlauten, Unicode und Encodings
Verfasst: Mittwoch 1. Februar 2006, 15:11
Von Python, Umlauten, Unicode und Encodings
Immer wieder tauchen Fragen zu Umlauten und Unicode auf. Mit dieser kurzen Anleitung versuche ich dieses Thema so einfach wie möglich zu erklären. Dabei geht es mir nicht um die korrekte Darstellung von dem was wirklich im Hintergrund passiert.
Umlaute im Python-Code
Python verwendet zwei verschiedene Objekttypen zum Speichern von Text. --> str und unicode.
Wenn man Python keine besonderen Anweisungen gibt, dann wird Text, den man einfach nur in Anführungszeichen schreibt, als Objekt vom Typ str abgespeichert (s = "Hallo Welt"). Dieser Typ kann normalerweise nur mit den Zeichen etwas anfangen, die auch im amerikanischen Englisch Gebrauch finden. Da es in der englischen Sprache keine Umlaute wie Ö oder Ä gibt, können diese, ohne besondere Vorkehrungen, nicht in ein Objekt
vom Typ str geschrieben werden.
Um trotzdem Umlaute verwenden zu können, hat man etwas eingeführt, das Python anzeigt in welchem Encoding das Python-Modul geschrieben wurde. Mit Hilfe eines Encoding Cookies zeigt man Python das Encoding des Python-Modules.
So könnte ein solches Cookie aussehen.
Dieses Encoding Cookie muss in der ersten oder zweiten Zeile im Modul stehen. Wichtig dabei ist allerdings, dass in diesem Encoding Cookie auch wirklich das Encoding angegeben wird, in dem die Datei abgespeichert wird. Es funktioniert nicht, wenn man dem Python-Interpreter mit # -*- coding: utf-8 -*- signalisiert, dass die Datei im UTF-8-Encoding abgespeichert wurde, wenn in Wirklichkeit die Datei als ISO-8895-1-Datei abgespeichert wird.
Viele einfache Editoren lassen einem keine Wahl -- besonders unter Windows ist das gerne der Fall. Wenn in einem deutschsprachigen Windows im Editor keine Auswahl getroffen werden kann, dann ist man mit dem Encoding Cookie # -*- coding: iso-8859-1 -*- nicht schlecht bedient. Eigentlich wäre das Encoding von Windows cp1252, aber darauf müssen wir hier nicht genauer eingehen.
Unter Linux können viele Editoren mit verschiedenen Encodings umgehen. Wenn man keine Auswahl trifft, dann wird die Datei im Standard-Encoding abgespeichert. Dieses Standard-Encoding findet man heraus, wenn man in einer Kommandozeilen-Konsole (Shell) den Befehl /usr/bin/locale eingibt:
Ab dem Moment, in dem das Encoding definiert wurde, können auch Umlaute und andere Sonderzeichen, die vom definierten Encoding abgedeckt werden, als String und in den Kommentaren Verwendung finden.
Vor der Definition des Encodings hätte Python eine Warnung angezeigt wenn man einen String mit Umlauten eingegeben hätte:
Nach der Definition des Encodings:
Umwandeln zwischen den verschiedenen Encodings
Python verwendet als Basis Unicode! So kurz dieser Satz auch ist, so wichtig ist er. Möchte man einen Text von einem Encoding in ein anderes Umwandeln, dann geschieht das in zwei Schritten.
Die Umwandlung nach Unicode wird von decode() und die Umwandlung von Unicode in ein anderes Encoding wird von encode() erledigt.
Aus diesem Beispiel lassen sich schon ein paar Dinge herauslesen.
- Wird das Encoding in einem Encoding Cookie angegeben, dann weiß Python,
mit welchem Encoding die Datei abgespeichert wurde und kann den Code
beim Interpretieren entsprechend umwandeln.
- Man muss beim Umwandeln nach Unicode, das Quell-Coding angeben.
- Man muss beim Umwandeln von Unicode, das Ziel-Coding angeben.
- Der Typ des Objektes sagt nichts über das verwendetet Encoding aus.
- Manche Encodings sind nicht in einer Konsole (Shell) darstellbar.
Welche Encodings funktionieren, ist abhängig vom Betriebssystem und den
Einstellungen der jeweiligen Konsole mit der das Programm gestartet
wurde. (Deshalb wurden die Print-Anweisungen in try und except
eingeschlossen.)
Man kann sich einen Arbeitsschritt sparen, wenn der Text direkt im Modul geschrieben wird. Man kann schon beim Definieren der Variable den Text nach Unicode umwandeln. Das sieht so aus:
Man erspart sich in unserem Fall die Umwandlung von iso-8859-1 nach Unicode. Durch das Encoding Cookie weiß Python, dass der String innerhalb der Anführungszeichen iso-8859-1 ist und kann deshalb ohne Angabe des Quell-Codings nach Unicode umwandeln. Es muss vor den Anführungszeichen einfach nur ein u geschrieben werden.
Umlaute in die Konsole schreiben (print)
Python läuft unter den verschiedensten Betriebssystemen. Deshalb ist es ein wenig schwieriger, sich auf ein einheitliches Encoding für die Kommandozeile zu einigen. Es läuft derzeit ein großer Umbruch von den verschiedenen Encodings, hin zu UTF-8. Mit UTF-8 als gemeinsames Encoding gäbe es keine Probleme mit den Umlauten. Leider kann das noch Jahre dauern, bis alle Betriebssysteme dieser Welt UTF-8 als Standard-Encoding verwenden.
Bis es so weit ist, müssen wir beim Programmieren auf dieses Thema eingehen und uns selbst darum kümmern, dass der Text den wir an die Konsole schicken, einem Encoding entspricht mit dem die Konsole etwas anfangen kann. Mit Konsole meine ich die Dos-Eingabeaufforderung sowie die verschiedensten Shells die man unter den anderen gängigen Betriebssystemen so verwendet.
Das Encoding der Standardausgabe (STDOUT) kann man so abfragen:
Das funktioniert aber nicht, wenn man ein Skript in einigen der üblichen IDEs startet, da diese meist STDOUT umleiten, so dass die Ausgabe innerhalb der IDE angezeigt wird. Das kann man meist so umgehen:
Wenn man einen Text mit Umlauten an die Konsole (in unserem Fall STDOUT) schicken möchte, dann muss man diesen Text in das Encoding der Konsole umwandeln. Wie ich bereits weiter oben schon beschrieben habe, muss
diese Umwandlung über Unicode laufen. Hier ein Beispiel:
Das gleiche Beispiel ein wenig kürzer (es entfällt die Umwandlung nach Unicode):
Umlaute von der Kommandozeile übernehmen
Wenn ein Programm über die Kommandozeile gestartet wird, dann kann man Parameter an das Programm übergeben. Diese können mit sys.argv abgefragt und verwendet werden.
Solch ein Aufruf könnte so aussehen:
Um mit diesen Argumenten etwas anfangen zu können, muss man den Text vom Encoding der Kommandozeile nach Unicode oder in das Encoding des Python-Skriptes umwandeln.
VORSICHT! Das Encoding der Kommandozeile ist nicht das Encoding von STDIN.
Das Encoding der Kommandozeile lässt sich mit sys.getfilesystemencoding() feststellen. Es ist also das Encoding, mit dem die Dateinamen der Dateien gespeichert werden.
Umlaute von Datenbanken oder Dateien
Die String-Daten, die man aus einer Datenbank liest, sind ziemlich sicher nicht im gleichen Encoding wie unser Skript. (durch Encoding Cookie definiert)
Meist kümmert sich die Schnittstelle darum, dass die Daten von der Datenbank nach Unicode umgewandelt werden. Das lässt sich leicht feststellen, indem man mit type() einen der Strings aus der Datenbank überprüft. Das hat den Vorteil, dass man weiß woran man ist und nicht mehr nach Unicode umwandeln muß, um zum gewünschten Ziel-Encoding zu gelangen.
Ist das nicht der Fall, muss man erst einmal feststellen, in welchem Encoding die Werte an das Python-Programm übergeben werden. Vielleicht gibt es einen Hinweis in der Beschreibung der Datenbank oder der Datenbankschnittstelle. Aber auch mit "Try and Error" lässt sich herausfinden, welches Encoding von der Datenbank verwendet wird. Hat man das Encoding herausgefunden, dann sollte man sich im Programm darum kümmern, dass die Strings vor der Verwendung nach Unicode oder in das Encoding des Skriptes umgewandelt werden.
Alles hier geschriebene gilt natürlich auch in umgekehrter Reihenfolge. Beim Schreiben in die Datenbank muss man sich natürlich auch darum kümmern, dass das richtige Encoding verwendet wird. Auch hier ist es wieder am Einfachsten, wenn sich bereits die Datenbankschnittstelle darum kümmert und man einfach mit Unicode arbeiten kann.
Lesen und Schreiben von Dateien
Normalerweise braucht man sich nicht um das Encoding der Dateien zu kümmern. Meist liest man die Datei mit dem gleichen Betriebssystem, mit dem die Datei geschrieben wurde.
Natürlich gibt es Ausnahmen und für solche Fälle gibt es das Modul codecs. Damit lassen sich Dateien mit den verschiedensten Encodings öffnen, lesen und schreiben. Man kann mit dem Befehl codecs.open() eine Datei öffnen und dabei gleich auch noch das Encoding angeben, mit dem in die Datei geschrieben wird.
Wird beim Schreiben in diese Datei ein Unicode-String übergeben, dann wird dieser automatisch in das vorher definierte Ziel-Encoding umgewandelt.
----
2006-02-01, gerold.penz@tirol.utanet.at
Falls ich beim Umwandeln nach BBcode einen Fehler gemacht habe --> füge ich noch den Originaltext bei, den ich in reStructuredText geschrieben habe.
http://paste.pocoo.org/show/750/ (veraltet)
Edit: reStructuredText ausgelagert.
Immer wieder tauchen Fragen zu Umlauten und Unicode auf. Mit dieser kurzen Anleitung versuche ich dieses Thema so einfach wie möglich zu erklären. Dabei geht es mir nicht um die korrekte Darstellung von dem was wirklich im Hintergrund passiert.
Umlaute im Python-Code
Python verwendet zwei verschiedene Objekttypen zum Speichern von Text. --> str und unicode.
Wenn man Python keine besonderen Anweisungen gibt, dann wird Text, den man einfach nur in Anführungszeichen schreibt, als Objekt vom Typ str abgespeichert (s = "Hallo Welt"). Dieser Typ kann normalerweise nur mit den Zeichen etwas anfangen, die auch im amerikanischen Englisch Gebrauch finden. Da es in der englischen Sprache keine Umlaute wie Ö oder Ä gibt, können diese, ohne besondere Vorkehrungen, nicht in ein Objekt
vom Typ str geschrieben werden.
Um trotzdem Umlaute verwenden zu können, hat man etwas eingeführt, das Python anzeigt in welchem Encoding das Python-Modul geschrieben wurde. Mit Hilfe eines Encoding Cookies zeigt man Python das Encoding des Python-Modules.
So könnte ein solches Cookie aussehen.
Code: Alles auswählen
# -*- coding: iso-8859-1 -*-
Viele einfache Editoren lassen einem keine Wahl -- besonders unter Windows ist das gerne der Fall. Wenn in einem deutschsprachigen Windows im Editor keine Auswahl getroffen werden kann, dann ist man mit dem Encoding Cookie # -*- coding: iso-8859-1 -*- nicht schlecht bedient. Eigentlich wäre das Encoding von Windows cp1252, aber darauf müssen wir hier nicht genauer eingehen.
Unter Linux können viele Editoren mit verschiedenen Encodings umgehen. Wenn man keine Auswahl trifft, dann wird die Datei im Standard-Encoding abgespeichert. Dieses Standard-Encoding findet man heraus, wenn man in einer Kommandozeilen-Konsole (Shell) den Befehl /usr/bin/locale eingibt:
Code: Alles auswählen
de_DE --> # -*- coding: iso-8859-1 -*-
de_DE@euro --> # -*- coding: iso-8859-15 -*-
de_DE.utf8 --> # -*- coding: utf-8 -*-
de_AT --> # -*- coding: iso-8859-1 -*-
de_AT@euro --> # -*- coding: iso-8859-15 -*-
de_AT.utf8 --> # -*- coding: utf-8 -*-
Vor der Definition des Encodings hätte Python eine Warnung angezeigt wenn man einen String mit Umlauten eingegeben hätte:
Code: Alles auswählen
s = "Hallo Welt" # OK
s = "Hallo Österreich" # Fehler/Warnung
Code: Alles auswählen
s = "Hallo Welt" # OK
s = "Hallo Österreich" # OK
Umwandeln zwischen den verschiedenen Encodings
Python verwendet als Basis Unicode! So kurz dieser Satz auch ist, so wichtig ist er. Möchte man einen Text von einem Encoding in ein anderes Umwandeln, dann geschieht das in zwei Schritten.
Code: Alles auswählen
iso-8859-1 --> Unicode --> utf-8
utf-8 --> Unicode --> iso-8859-1
...
Code: Alles auswählen
#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-
# Text definieren
s_iso88591 = "Hallo Österreich"
# Text nach Unicode umwandeln
s_unicode = s_iso88591.decode("iso-8859-1")
# Text nach UTF-8 umwandeln
s_utf8 = s_unicode.encode("utf-8")
print type(s_iso88591)
print type(s_unicode)
print type(s_utf8)
try:
print s_iso88591
except:
print "Nicht darstellbar..."
try:
print s_unicode
except:
print "Nicht darstellbar..."
try:
print s_utf8
except:
print "Nicht darstellbar..."
- Wird das Encoding in einem Encoding Cookie angegeben, dann weiß Python,
mit welchem Encoding die Datei abgespeichert wurde und kann den Code
beim Interpretieren entsprechend umwandeln.
- Man muss beim Umwandeln nach Unicode, das Quell-Coding angeben.
- Man muss beim Umwandeln von Unicode, das Ziel-Coding angeben.
- Der Typ des Objektes sagt nichts über das verwendetet Encoding aus.
- Manche Encodings sind nicht in einer Konsole (Shell) darstellbar.
Welche Encodings funktionieren, ist abhängig vom Betriebssystem und den
Einstellungen der jeweiligen Konsole mit der das Programm gestartet
wurde. (Deshalb wurden die Print-Anweisungen in try und except
eingeschlossen.)
Man kann sich einen Arbeitsschritt sparen, wenn der Text direkt im Modul geschrieben wird. Man kann schon beim Definieren der Variable den Text nach Unicode umwandeln. Das sieht so aus:
Code: Alles auswählen
s_unicode = u"Hallo Österreich"
Umlaute in die Konsole schreiben (print)
Python läuft unter den verschiedensten Betriebssystemen. Deshalb ist es ein wenig schwieriger, sich auf ein einheitliches Encoding für die Kommandozeile zu einigen. Es läuft derzeit ein großer Umbruch von den verschiedenen Encodings, hin zu UTF-8. Mit UTF-8 als gemeinsames Encoding gäbe es keine Probleme mit den Umlauten. Leider kann das noch Jahre dauern, bis alle Betriebssysteme dieser Welt UTF-8 als Standard-Encoding verwenden.
Bis es so weit ist, müssen wir beim Programmieren auf dieses Thema eingehen und uns selbst darum kümmern, dass der Text den wir an die Konsole schicken, einem Encoding entspricht mit dem die Konsole etwas anfangen kann. Mit Konsole meine ich die Dos-Eingabeaufforderung sowie die verschiedensten Shells die man unter den anderen gängigen Betriebssystemen so verwendet.
Das Encoding der Standardausgabe (STDOUT) kann man so abfragen:
Code: Alles auswählen
stdout_encoding = sys.stdout.encoding
Code: Alles auswählen
stdout_encoding = sys.stdout.encoding or sys.getfilesystemencoding()
diese Umwandlung über Unicode laufen. Hier ein Beispiel:
Code: Alles auswählen
#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-
import sys
# Encoding der Standardausgabe herausfinden
stdout_encoding = sys.stdout.encoding or sys.getfilesystemencoding()
# Nachricht definieren (durch das Encoding Cookie als iso-8859-1 gekennzeichnet)
message = "Hallo Österreich"
# Nachricht umwandeln (iso-8859-1 -> unicode -> Ausgabe-Encoding) und
# mit print an die Standardausgabe übergeben.
print message.decode("iso-8859-1").encode(stdout_encoding)
Code: Alles auswählen
#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-
import sys
# Encoding der Standardausgabe herausfinden
stdout_encoding = sys.stdout.encoding or sys.getfilesystemencoding()
# Nachricht definieren (durch das Encoding Cookie als iso-8859-1 gekennzeichnet)
message = u"Hallo Österreich"
# Nachricht umwandeln (unicode -> Ausgabe-Encoding) und
# mit print an die Standardausgabe übergeben.
print message.encode(stdout_encoding)
Umlaute von der Kommandozeile übernehmen
Wenn ein Programm über die Kommandozeile gestartet wird, dann kann man Parameter an das Programm übergeben. Diese können mit sys.argv abgefragt und verwendet werden.
Solch ein Aufruf könnte so aussehen:
Code: Alles auswählen
python mein_skript.py "Hallo Österreich" "Über den Wolken"
VORSICHT! Das Encoding der Kommandozeile ist nicht das Encoding von STDIN.
Das Encoding der Kommandozeile lässt sich mit sys.getfilesystemencoding() feststellen. Es ist also das Encoding, mit dem die Dateinamen der Dateien gespeichert werden.
Code: Alles auswählen
#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-
import sys
# Encoding der Standardausgabe und des Dateisystems herausfinden
stdout_encoding = sys.stdout.encoding or sys.getfilesystemencoding()
fs_encoding = sys.getfilesystemencoding()
# Kompletten Kommandozeilenaufruf übernehmen und nach Unicode umwandeln
cmd_original = " ".join(sys.argv)
cmd_unicode = cmd_original.decode(fs_encoding)
# String verändern und wieder an die Kommandozeile übergeben
cmd_unicode += u" (öäü)"
print cmd_unicode.encode(stdout_encoding)
Umlaute von Datenbanken oder Dateien
Die String-Daten, die man aus einer Datenbank liest, sind ziemlich sicher nicht im gleichen Encoding wie unser Skript. (durch Encoding Cookie definiert)
Meist kümmert sich die Schnittstelle darum, dass die Daten von der Datenbank nach Unicode umgewandelt werden. Das lässt sich leicht feststellen, indem man mit type() einen der Strings aus der Datenbank überprüft. Das hat den Vorteil, dass man weiß woran man ist und nicht mehr nach Unicode umwandeln muß, um zum gewünschten Ziel-Encoding zu gelangen.
Ist das nicht der Fall, muss man erst einmal feststellen, in welchem Encoding die Werte an das Python-Programm übergeben werden. Vielleicht gibt es einen Hinweis in der Beschreibung der Datenbank oder der Datenbankschnittstelle. Aber auch mit "Try and Error" lässt sich herausfinden, welches Encoding von der Datenbank verwendet wird. Hat man das Encoding herausgefunden, dann sollte man sich im Programm darum kümmern, dass die Strings vor der Verwendung nach Unicode oder in das Encoding des Skriptes umgewandelt werden.
Alles hier geschriebene gilt natürlich auch in umgekehrter Reihenfolge. Beim Schreiben in die Datenbank muss man sich natürlich auch darum kümmern, dass das richtige Encoding verwendet wird. Auch hier ist es wieder am Einfachsten, wenn sich bereits die Datenbankschnittstelle darum kümmert und man einfach mit Unicode arbeiten kann.
Lesen und Schreiben von Dateien
Normalerweise braucht man sich nicht um das Encoding der Dateien zu kümmern. Meist liest man die Datei mit dem gleichen Betriebssystem, mit dem die Datei geschrieben wurde.
Natürlich gibt es Ausnahmen und für solche Fälle gibt es das Modul codecs. Damit lassen sich Dateien mit den verschiedensten Encodings öffnen, lesen und schreiben. Man kann mit dem Befehl codecs.open() eine Datei öffnen und dabei gleich auch noch das Encoding angeben, mit dem in die Datei geschrieben wird.
Wird beim Schreiben in diese Datei ein Unicode-String übergeben, dann wird dieser automatisch in das vorher definierte Ziel-Encoding umgewandelt.
Code: Alles auswählen
#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-
import sys
import codecs
# Encoding der Standardausgabe herausfinden
stdout_encoding = sys.stdout.encoding or sys.getfilesystemencoding()
# Datei schreiben
f = codecs.open("dateiname.txt", "w", "utf-8")
message = u"Über den Wolken"
f.write(message)
f.close()
# Datei lesen
f = codecs.open("dateiname.txt", "r", "utf-8")
s_unicode = f.read()
f.close()
# In die Konsole schreiben
print s_unicode.encode(stdout_encoding)
----
2006-02-01, gerold.penz@tirol.utanet.at
Falls ich beim Umwandeln nach BBcode einen Fehler gemacht habe --> füge ich noch den Originaltext bei, den ich in reStructuredText geschrieben habe.
http://paste.pocoo.org/show/750/ (veraltet)
Edit: reStructuredText ausgelagert.