Unicode-HOWTO¶
Dieses HOWTO behandelt die Unterstützung der Unicode-Spezifikation zur Darstellung von Textdaten in Python und erklärt verschiedene Probleme, auf die man bei der Arbeit mit Unicode häufig stößt.
Einführung in Unicode¶
Begriffsbestimmungen¶
Heutige Programme müssen eine große Vielfalt von Zeichen verarbeiten können. Anwendungen werden oft internationalisiert, um Meldungen und Ausgaben in einer Reihe vom Benutzer wählbarer Sprachen anzuzeigen. Dasselbe Programm muss eine Fehlermeldung möglicherweise auf Englisch, Französisch, Japanisch, Hebräisch oder Russisch ausgeben. Webinhalte können in jeder dieser Sprachen geschrieben sein und auch eine Vielzahl von Emoji-Symbolen enthalten. Der Zeichenkettentyp von Python verwendet zur Darstellung von Zeichen den Unicode-Standard, sodass Python-Programme mit all diesen möglichen Zeichen arbeiten können.
Unicode (https://www.unicode.org/) ist eine Spezifikation, die jedes von menschlichen Sprachen verwendete Zeichen auflisten und jedem Zeichen einen eigenen eindeutigen Code geben will. Die Unicode-Spezifikationen werden fortlaufend überarbeitet und aktualisiert, um neue Sprachen und Symbole zu ergänzen.
Ein Zeichen ist der kleinstmögliche Bestandteil eines Textes. „A“, „B“, „C“ usw. sind alle verschiedene Zeichen. Ebenso „È“ und „Í“. Was als Zeichen gilt, hängt von der Sprache oder dem Zusammenhang ab, über den man spricht. So gibt es zum Beispiel ein Zeichen für „Roman Numeral One“, „Ⅰ“, das vom Großbuchstaben „I“ getrennt ist. Sie sehen üblicherweise gleich aus, aber es sind zwei verschiedene Zeichen mit verschiedenen Bedeutungen.
Der Unicode-Standard beschreibt, wie Zeichen durch Codepunkte dargestellt werden. Ein Codepunktwert ist eine Ganzzahl im Bereich von 0 bis 0x10FFFF (etwa 1,1 Millionen Werte – die tatsächlich zugewiesene Anzahl ist geringer). Im Standard und in diesem Dokument wird ein Codepunkt in der Schreibweise U+265E notiert, womit das Zeichen mit dem Wert 0x265e (dezimal 9822) gemeint ist.
Der Unicode-Standard enthält viele Tabellen, die Zeichen und ihre zugehörigen Codepunkte auflisten:
0061 'a'; LATIN SMALL LETTER A
0062 'b'; LATIN SMALL LETTER B
0063 'c'; LATIN SMALL LETTER C
...
007B '{'; LEFT CURLY BRACKET
...
2167 'Ⅷ'; ROMAN NUMERAL EIGHT
2168 'Ⅸ'; ROMAN NUMERAL NINE
...
265E '♞'; BLACK CHESS KNIGHT
265F '♟'; BLACK CHESS PAWN
...
1F600 '😀'; GRINNING FACE
1F609 '😉'; WINKING FACE
...
Streng genommen folgt aus diesen Begriffsbestimmungen, dass es sinnlos ist zu sagen: „Das ist Zeichen U+265E“. U+265E ist ein Codepunkt, der ein bestimmtes Zeichen darstellt. In diesem Fall stellt er das Zeichen „BLACK CHESS KNIGHT“, „♞“, dar. In formlosen Zusammenhängen wird dieser Unterschied zwischen Codepunkten und Zeichen manchmal vergessen.
Ein Zeichen wird auf einem Bildschirm oder auf Papier durch eine Menge grafischer Elemente dargestellt, die man Glyphe nennt. Die Glyphe für ein großes A besteht zum Beispiel aus zwei diagonalen und einem waagerechten Strich, wobei die genauen Einzelheiten von der verwendeten Schriftart abhängen. Der meiste Python-Code muss sich um Glyphen nicht kümmern. Die richtige Glyphe zur Anzeige herauszufinden ist im Allgemeinen Aufgabe eines GUI-Werkzeugkastens oder des Schriftzeichners eines Terminals.
Kodierungen¶
Um den vorherigen Abschnitt zusammenzufassen: Eine Unicode-Zeichenkette ist eine Folge von Codepunkten, also von Zahlen von 0 bis 0x10FFFF (dezimal 1.114.111). Diese Folge von Codepunkten muss im Speicher als Menge von Codeeinheiten dargestellt werden, und Codeeinheiten werden dann auf 8-Bit-Bytes abgebildet. Die Regeln, um eine Unicode-Zeichenkette in eine Folge von Bytes zu übersetzen, nennt man Zeichenkodierung oder einfach Kodierung.
Die erste Kodierung, an die du denken könntest, verwendet 32-Bit-Ganzzahlen als Codeeinheit und dann die Darstellung von 32-Bit-Ganzzahlen durch die CPU. In dieser Darstellung könnte die Zeichenkette „Python“ so aussehen:
P y t h o n
0x50 00 00 00 79 00 00 00 74 00 00 00 68 00 00 00 6f 00 00 00 6e 00 00 00
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
Diese Darstellung ist einfach, bringt aber eine Reihe von Problemen mit sich.
Sie ist nicht portabel. Verschiedene Prozessoren ordnen die Bytes unterschiedlich an.
Sie verschwendet sehr viel Platz. In den meisten Texten sind die meisten Codepunkte kleiner als 127 oder kleiner als 255, es wird also viel Platz von
0x00-Bytes belegt. Die obige Zeichenkette braucht 24 Bytes gegenüber den 6 Bytes, die eine ASCII-Darstellung braucht. Der höhere Arbeitsspeicherverbrauch spielt keine große Rolle (Arbeitsplatzrechner haben Gigabytes an Arbeitsspeicher, und Zeichenketten sind üblicherweise nicht so groß), aber unseren Verbrauch an Festplattenplatz und Netzwerkbandbreite um den Faktor 4 zu erhöhen ist nicht tragbar.Sie ist nicht mit bestehenden C-Funktionen wie
strlen()verträglich, es müsste also eine neue Familie von Funktionen für breite Zeichenketten verwendet werden.
Diese Kodierung wird daher nicht sehr viel verwendet, und man wählt stattdessen andere Kodierungen, die effizienter und bequemer sind, etwa UTF-8.
UTF-8 ist eine der am häufigsten verwendeten Kodierungen, und Python verwendet sie oft standardmäßig. UTF steht für „Unicode Transformation Format“, und die „8“ bedeutet, dass in der Kodierung 8-Bit-Werte verwendet werden. (Es gibt auch die Kodierungen UTF-16 und UTF-32, die aber seltener verwendet werden als UTF-8.) UTF-8 verwendet die folgenden Regeln:
Ist der Codepunkt < 128, wird er durch den entsprechenden Bytewert dargestellt.
Ist der Codepunkt >= 128, wird er in eine Folge von zwei, drei oder vier Bytes verwandelt, wobei jedes Byte der Folge zwischen 128 und 255 liegt.
UTF-8 hat mehrere bequeme Eigenschaften:
Es kann mit jedem Unicode-Codepunkt umgehen.
Eine Unicode-Zeichenkette wird in eine Folge von Bytes verwandelt, die eingebettete Nullbytes nur dort enthält, wo sie das Nullzeichen (U+0000) darstellen. Das heißt, dass UTF-8-Zeichenketten von C-Funktionen wie
strcpy()verarbeitet und über Protokolle gesendet werden können, die Nullbytes nur als Endemarkierung einer Zeichenkette verkraften.Eine Zeichenkette aus ASCII-Text ist auch gültiger UTF-8-Text.
UTF-8 ist recht kompakt. Die meisten häufig verwendeten Zeichen lassen sich mit einem oder zwei Bytes darstellen.
Werden Bytes beschädigt oder gehen verloren, lässt sich der Anfang des nächsten UTF-8-kodierten Codepunkts bestimmen und die Synchronisierung wiederherstellen. Es ist außerdem unwahrscheinlich, dass zufällige 8-Bit-Daten wie gültiges UTF-8 aussehen.
UTF-8 ist eine byteorientierte Kodierung. Die Kodierung legt fest, dass jedes Zeichen durch eine bestimmte Folge von einem oder mehreren Bytes dargestellt wird. Das vermeidet die Probleme mit der Byte-Reihenfolge, die bei ganzzahl- und wortorientierten Kodierungen wie UTF-16 und UTF-32 auftreten können, bei denen die Folge der Bytes davon abhängt, auf welcher Hardware die Zeichenkette kodiert wurde.
Quellen¶
Die Seite des Unicode-Konsortiums hat Zeichentabellen, ein Glossar und PDF-Fassungen der Unicode-Spezifikation. Sei auf etwas schwierige Lektüre vorbereitet. Auf der Seite gibt es außerdem eine Zeittafel zur Entstehung und Entwicklung von Unicode.
Auf dem YouTube-Kanal Computerphile erörtert Tom Scott kurz die Geschichte von Unicode und UTF-8 (9 Minuten 36 Sekunden).
Als Hilfe zum Verständnis des Standards hat Jukka Korpela eine einführende Anleitung zum Lesen der Unicode-Zeichentabellen geschrieben.
Ein weiterer guter einführender Artikel wurde von Joel Spolsky geschrieben. Wenn diese Einführung dir die Dinge nicht klar gemacht hat, solltest du diesen anderen Artikel lesen, bevor du weitermachst.
Wikipedia-Einträge sind oft hilfreich. Siehe zum Beispiel die Einträge zu „character encoding“ und UTF-8.
Die Unicode-Unterstützung von Python¶
Nachdem du die Grundzüge von Unicode gelernt hast, können wir uns die Unicode-Funktionen von Python ansehen.
Der Zeichenkettentyp¶
Seit Python 3.0 enthält der Typ str der Sprache Unicode-Zeichen. Jede Zeichenkette, die mit "unicode rocks!", 'unicode rocks!' oder der Syntax mit dreifachen Anführungszeichen erzeugt wird, ist also als Unicode gespeichert.
Die Standardkodierung für Python-Quellcode ist UTF-8, du kannst ein Unicode-Zeichen also einfach in ein Zeichenkettenliteral schreiben:
try:
with open('/tmp/input.txt', 'r') as f:
...
except OSError:
# 'File not found' error message.
print("Fichier non trouvé")
Nebenbei: Python 3 unterstützt auch Unicode-Zeichen in Bezeichnern:
répertoire = "/tmp/records.log"
with open(répertoire, "w") as f:
f.write("test\n")
Wenn du ein bestimmtes Zeichen in deinem Editor nicht eingeben kannst oder den Quellcode aus irgendeinem Grund rein in ASCII halten willst, kannst du in Zeichenkettenliteralen auch Escape-Sequenzen verwenden. (Je nach System siehst du statt eines u-Escapes vielleicht die tatsächliche Glyphe des großen Delta.)
>>> "\N{GREEK CAPITAL LETTER DELTA}" # Using the character name
'\u0394'
>>> "\u0394" # Using a 16-bit hex value
'\u0394'
>>> "\U00000394" # Using a 32-bit hex value
'\u0394'
Außerdem lässt sich eine Zeichenkette über die Methode decode() von bytes erzeugen. Diese Methode nimmt ein Argument encoding, etwa UTF-8, und wahlweise ein Argument errors.
Das Argument errors legt fest, was geschieht, wenn die Eingabezeichenkette nicht nach den Regeln der Kodierung umgewandelt werden kann. Zulässige Werte für dieses Argument sind 'strict' (löst eine UnicodeDecodeError-Ausnahme aus), 'replace' (verwendet U+FFFD, REPLACEMENT CHARACTER), 'ignore' (lässt das Zeichen im Unicode-Ergebnis einfach weg) oder 'backslashreplace' (fügt eine \xNN-Escape-Sequenz ein). Die folgenden Beispiele zeigen die Unterschiede:
>>> b'\x80abc'.decode("utf-8", "strict")
Traceback (most recent call last):
...
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x80 in position 0:
invalid start byte
>>> b'\x80abc'.decode("utf-8", "replace")
'\ufffdabc'
>>> b'\x80abc'.decode("utf-8", "backslashreplace")
'\\x80abc'
>>> b'\x80abc'.decode("utf-8", "ignore")
'abc'
Kodierungen werden als Zeichenketten angegeben, die den Namen der Kodierung enthalten. Python bringt rund 100 verschiedene Kodierungen mit. Eine Liste steht in der Python-Bibliotheksreferenz unter Standard Encodings. Manche Kodierungen haben mehrere Namen. So sind zum Beispiel 'latin-1', 'iso_8859_1' und '8859‘ alle gleichbedeutend mit derselben Kodierung.
Unicode-Zeichenketten mit einem Zeichen lassen sich auch mit der integrierten Funktion chr() erzeugen, die Ganzzahlen nimmt und eine Unicode-Zeichenkette der Länge 1 zurückgibt, die den entsprechenden Codepunkt enthält. Der umgekehrte Vorgang ist die integrierte Funktion ord(), die eine Unicode-Zeichenkette mit einem Zeichen nimmt und den Codepunktwert zurückgibt:
>>> chr(57344)
'\ue000'
>>> ord('\ue000')
57344
In Bytes umwandeln¶
Das Gegenstück zu bytes.decode() ist str.encode(), das eine bytes-Darstellung der Unicode-Zeichenkette zurückgibt, kodiert in der gewünschten encoding.
Der Parameter errors ist derselbe wie der Parameter der Methode decode(), unterstützt aber ein paar weitere mögliche Behandlungen. Neben 'strict', 'ignore' und 'replace' (das in diesem Fall ein Fragezeichen anstelle des nicht kodierbaren Zeichens einfügt) gibt es außerdem 'xmlcharrefreplace' (fügt eine XML-Zeichenreferenz ein), backslashreplace (fügt eine \uNNNN-Escape-Sequenz ein) und namereplace (fügt eine \N{...}-Escape-Sequenz ein).
Das folgende Beispiel zeigt die unterschiedlichen Ergebnisse:
>>> u = chr(40960) + 'abcd' + chr(1972)
>>> u.encode('utf-8')
b'\xea\x80\x80abcd\xde\xb4'
>>> u.encode('ascii')
Traceback (most recent call last):
...
UnicodeEncodeError: 'ascii' codec can't encode character '\ua000' in
position 0: ordinal not in range(128)
>>> u.encode('ascii', 'ignore')
b'abcd'
>>> u.encode('ascii', 'replace')
b'?abcd?'
>>> u.encode('ascii', 'xmlcharrefreplace')
b'ꀀabcd޴'
>>> u.encode('ascii', 'backslashreplace')
b'\\ua000abcd\\u07b4'
>>> u.encode('ascii', 'namereplace')
b'\\N{YI SYLLABLE IT}abcd\\u07b4'
Die tiefliegenden Routinen zum Registrieren und Verwenden der verfügbaren Kodierungen finden sich im Modul codecs. Neue Kodierungen umzusetzen erfordert ebenfalls ein Verständnis des Moduls codecs. Allerdings liegen die Kodier- und Dekodierfunktionen, die dieses Modul zurückgibt, üblicherweise tiefer als bequem ist, und neue Kodierungen zu schreiben ist eine Spezialaufgabe. Das Modul wird in diesem HOWTO daher nicht behandelt.
Unicode-Literale im Python-Quellcode¶
Im Python-Quellcode lassen sich bestimmte Unicode-Codepunkte mit der Escape-Sequenz \u schreiben, auf die vier Hexadezimalziffern folgen, die den Codepunkt angeben. Die Escape-Sequenz \U ist ähnlich, erwartet aber acht Hexadezimalziffern und nicht vier:
>>> s = "a\xac\u1234\u20ac\U00008000"
... # ^^^^ two-digit hex escape
... # ^^^^^^ four-digit Unicode escape
... # ^^^^^^^^^^ eight-digit Unicode escape
>>> [ord(c) for c in s]
[97, 172, 4660, 8364, 32768]
Escape-Sequenzen für Codepunkte größer als 127 sind in kleinen Dosen in Ordnung, werden aber lästig, wenn du viele Zeichen mit Akzenten verwendest – wie in einem Programm mit Meldungen auf Französisch oder in einer anderen Sprache mit Akzenten. Du kannst Zeichenketten auch mit der integrierten Funktion chr() zusammensetzen, aber das ist noch mühsamer.
Im Idealfall willst du Literale in der natürlichen Kodierung deiner Sprache schreiben können. Du könntest den Python-Quellcode dann mit deinem Lieblingseditor bearbeiten, der die Zeichen mit Akzenten natürlich anzeigt, und zur Laufzeit würden die richtigen Zeichen verwendet.
Python unterstützt standardmäßig das Schreiben von Quellcode in UTF-8, aber du kannst fast jede Kodierung verwenden, wenn du die verwendete Kodierung angibst. Das geschieht über einen besonderen Kommentar in der ersten oder zweiten Zeile der Quelldatei:
#!/usr/bin/env python
# -*- coding: latin-1 -*-
u = 'abcdé'
print(ord(u[-1]))
Die Syntax ist von der Notation in Emacs zur Angabe dateilokaler Variablen angeregt. Emacs unterstützt viele verschiedene Variablen, Python aber nur „coding“. Die Zeichen -*- zeigen Emacs an, dass der Kommentar besonders ist. Für Python haben sie keine Bedeutung, sind aber Konvention. Python sucht im Kommentar nach coding: name oder coding=name.
Wenn du keinen solchen Kommentar einfügst, wird wie schon erwähnt UTF-8 als Standardkodierung verwendet. Weitere Informationen stehen auch in PEP 263.
Unicode-Eigenschaften¶
Die Unicode-Spezifikation enthält eine Datenbank mit Informationen über Codepunkte. Für jeden festgelegten Codepunkt umfassen die Informationen den Namen des Zeichens, seine Kategorie und, wo anwendbar, den Zahlenwert (bei Zeichen, die Zahlbegriffe darstellen, etwa die römischen Ziffern oder Brüche wie ein Drittel und vier Fünftel). Es gibt außerdem Eigenschaften zur Darstellung, etwa wie der Codepunkt in bidirektionalem Text zu verwenden ist.
Das folgende Programm zeigt einige Informationen über mehrere Zeichen und gibt den Zahlenwert eines bestimmten Zeichens aus:
import unicodedata
u = chr(233) + chr(0x0bf2) + chr(3972) + chr(6000) + chr(13231)
for i, c in enumerate(u):
print(i, '%04x' % ord(c), unicodedata.category(c), end=" ")
print(unicodedata.name(c))
# Get numeric value of second character
print(unicodedata.numeric(u[1]))
Ausgeführt gibt das Folgendes aus:
0 00e9 Ll LATIN SMALL LETTER E WITH ACUTE
1 0bf2 No TAMIL NUMBER ONE THOUSAND
2 0f84 Mn TIBETAN MARK HALANTA
3 1770 Lo TAGBANWA LETTER SA
4 33af So SQUARE RAD OVER S SQUARED
1000.0
Die Kategoriecodes sind Abkürzungen, die die Art des Zeichens beschreiben. Sie sind in Kategorien wie „Letter“, „Number“, „Punctuation“ oder „Symbol“ gruppiert, die wiederum in Unterkategorien aufgeteilt sind. Nehmen wir die Codes aus der obigen Ausgabe: 'Ll' bedeutet „Letter, lowercase“, 'No' bedeutet „Number, other“, 'Mn' ist „Mark, nonspacing“ und 'So' ist „Symbol, other“. Eine Liste der Kategoriecodes steht im Abschnitt General Category Values der Dokumentation zur Unicode Character Database.
Zeichenketten vergleichen¶
Unicode macht den Vergleich von Zeichenketten etwas komplizierter, weil dieselbe Zeichenmenge durch unterschiedliche Folgen von Codepunkten dargestellt werden kann. So lässt sich ein Buchstabe wie „ê“ als einzelner Codepunkt U+00EA darstellen oder als U+0065 U+0302, also als Codepunkt für „e“ gefolgt von einem Codepunkt für „COMBINING CIRCUMFLEX ACCENT“. Ausgegeben erzeugen sie dieselbe Ausgabe, aber eine Zeichenkette hat die Länge 1 und die andere die Länge 2.
Ein Werkzeug für einen Vergleich ohne Beachtung der Groß- und Kleinschreibung ist die Zeichenkettenmethode casefold(), die eine Zeichenkette nach einem im Unicode-Standard beschriebenen Algorithmus in eine Form ohne Groß- und Kleinschreibung umwandelt. Dieser Algorithmus behandelt Zeichen wie den deutschen Buchstaben „ß“ (Codepunkt U+00DF) besonders: Daraus wird das Kleinbuchstabenpaar „ss“.
>>> street = 'Gürzenichstraße'
>>> street.casefold()
'gürzenichstrasse'
Ein zweites Werkzeug ist die Funktion normalize() des Moduls unicodedata, die Zeichenketten in eine von mehreren Normalformen umwandelt, in denen Buchstaben, auf die ein kombinierendes Zeichen folgt, durch einzelne Zeichen ersetzt werden. normalize() lässt sich verwenden, um Zeichenketten zu vergleichen, ohne fälschlich Ungleichheit zu melden, wenn zwei Zeichenketten kombinierende Zeichen unterschiedlich verwenden:
import unicodedata
def compare_strs(s1, s2):
def NFD(s):
return unicodedata.normalize('NFD', s)
return NFD(s1) == NFD(s2)
single_char = 'ê'
multiple_chars = '\N{LATIN SMALL LETTER E}\N{COMBINING CIRCUMFLEX ACCENT}'
print('length of first string=', len(single_char))
print('length of second string=', len(multiple_chars))
print(compare_strs(single_char, multiple_chars))
Ausgeführt gibt das Folgendes aus:
$ python compare-strs.py
length of first string= 1
length of second string= 2
True
Das erste Argument der Funktion normalize() ist eine Zeichenkette, die die gewünschte Normalform angibt. Sie kann „NFC“, „NFKC“, „NFD“ oder „NFKD“ sein.
Der Unicode-Standard legt außerdem fest, wie Vergleiche ohne Beachtung der Groß- und Kleinschreibung durchzuführen sind:
import unicodedata
def compare_caseless(s1, s2):
def NFD(s):
return unicodedata.normalize('NFD', s)
return NFD(NFD(s1).casefold()) == NFD(NFD(s2).casefold())
# Example usage
single_char = 'ê'
multiple_chars = '\N{LATIN CAPITAL LETTER E}\N{COMBINING CIRCUMFLEX ACCENT}'
print(compare_caseless(single_char, multiple_chars))
Das gibt True aus. (Warum wird NFD() zweimal aufgerufen? Weil es ein paar Zeichen gibt, bei denen casefold() eine nicht normalisierte Zeichenkette zurückgibt. Das Ergebnis muss daher erneut normalisiert werden. Eine Erörterung und ein Beispiel stehen in Abschnitt 3.13 des Unicode-Standards.)
Unicode-reguläre-Ausdrücke¶
Die regulären Ausdrücke, die das Modul re unterstützt, lassen sich entweder als Bytes oder als Zeichenketten übergeben. Einige der besonderen Zeichenfolgen wie \d und \w haben unterschiedliche Bedeutungen, je nachdem, ob das Muster als Bytes oder als Zeichenkette übergeben wird. So passt \d bei Bytes auf die Zeichen [0-9], bei Zeichenketten aber auf jedes Zeichen, das in der Kategorie 'Nd' liegt.
Die Zeichenkette in diesem Beispiel enthält die Zahl 57, geschrieben sowohl in thailändischen als auch in arabischen Ziffern:
import re
p = re.compile(r'\d+')
s = "Over \u0e55\u0e57 57 flavours"
m = p.search(s)
print(repr(m.group()))
Ausgeführt passt \d+ auf die thailändischen Ziffern und gibt sie aus. Übergibst du compile() das Flag re.ASCII, passt \d+ stattdessen auf die Teilzeichenkette „57“.
Ebenso passt \w auf eine große Vielfalt von Unicode-Zeichen, bei Bytes oder bei gesetztem re.ASCII aber nur auf [a-zA-Z0-9_], und \s passt entweder auf Unicode-Leerraumzeichen oder auf [ \t\n\r\f\v].
Quellen¶
Einige gute weitere Erörterungen der Unicode-Unterstützung von Python sind:
Processing Text Files in Python 3 von Nick Coghlan.
Pragmatic Unicode, ein Vortrag von Ned Batchelder auf der PyCon 2012.
Der Typ str ist in der Python-Bibliotheksreferenz unter Textsequenztyp – str beschrieben.
Die Dokumentation des Moduls unicodedata.
Die Dokumentation des Moduls codecs.
Marc-André Lemburg hielt auf der EuroPython 2002 einen Vortrag mit dem Titel „Python and Unicode“ (PDF-Folien). Die Folien sind ein ausgezeichneter Überblick über den Entwurf der Unicode-Funktionen von Python 2 (wo der Unicode-Zeichenkettentyp unicode heißt und Literale mit u beginnen).
Unicode-Daten lesen und schreiben¶
Sobald du etwas Code geschrieben hast, der mit Unicode-Daten arbeitet, ist das nächste Problem die Ein- und Ausgabe. Wie bekommst du Unicode-Zeichenketten in dein Programm, und wie wandelst du Unicode in eine Form um, die sich zum Speichern oder Übertragen eignet?
Möglicherweise musst du je nach deinen Eingabequellen und Ausgabezielen gar nichts tun. Du solltest prüfen, ob die in deiner Anwendung verwendeten Bibliotheken Unicode von sich aus unterstützen. XML-Parser geben zum Beispiel oft Unicode-Daten zurück. Viele relationale Datenbanken unterstützen ebenfalls Spalten mit Unicode-Werten und können Unicode-Werte aus einer SQL-Abfrage zurückgeben.
Unicode-Daten werden üblicherweise in eine bestimmte Kodierung umgewandelt, bevor sie auf die Festplatte geschrieben oder über einen Socket gesendet werden. Es ist möglich, die ganze Arbeit selbst zu machen: eine Datei öffnen, ein 8-Bit-bytes-Objekt daraus lesen und die Bytes mit bytes.decode(encoding) umwandeln. Dieser Weg von Hand ist allerdings nicht empfehlenswert.
Ein Problem ist die Mehrbyte-Natur von Kodierungen. Ein Unicode-Zeichen kann durch mehrere Bytes dargestellt werden. Wenn du die Datei in Blöcken beliebiger Größe lesen willst (sagen wir 1024 oder 4096 Bytes), musst du Code zur Fehlerbehandlung schreiben, der den Fall abfängt, dass am Ende eines Blocks nur ein Teil der Bytes gelesen wird, die ein einzelnes Unicode-Zeichen kodieren. Eine Lösung wäre, die ganze Datei in den Speicher zu lesen und dann zu dekodieren, aber das verhindert die Arbeit mit extrem großen Dateien. Musst du eine 2-GiB-Datei lesen, brauchst du 2 GiB Arbeitsspeicher. (Eigentlich mehr, weil du zumindest kurzzeitig sowohl die kodierte Zeichenkette als auch ihre Unicode-Fassung im Speicher hättest.)
Die Lösung wäre, die tiefliegende Dekodierschnittstelle zu verwenden, um den Fall unvollständiger Kodierfolgen abzufangen. Diese Arbeit ist schon für dich erledigt: Die integrierte Funktion open() kann ein dateiartiges Objekt zurückgeben, das davon ausgeht, dass der Inhalt der Datei in einer angegebenen Kodierung vorliegt, und das für Methoden wie read() und write() Unicode-Parameter annimmt. Das funktioniert über die Parameter encoding und errors von open(), die genauso ausgelegt werden wie die in str.encode() und bytes.decode().
Unicode aus einer Datei zu lesen ist daher einfach:
with open('unicode.txt', encoding='utf-8') as f:
for line in f:
print(repr(line))
Es ist auch möglich, Dateien im Aktualisierungsmodus zu öffnen und damit sowohl zu lesen als auch zu schreiben:
with open('test', encoding='utf-8', mode='w+') as f:
f.write('\u4500 blah blah blah\n')
f.seek(0)
print(repr(f.readline()[:1]))
Das Unicode-Zeichen U+FEFF wird als Byte-Reihenfolge-Markierung (BOM) verwendet und oft als erstes Zeichen einer Datei geschrieben, um die automatische Erkennung der Byte-Reihenfolge der Datei zu unterstützen. Manche Kodierungen, etwa UTF-16, erwarten eine BOM am Anfang einer Datei. Wird eine solche Kodierung verwendet, wird die BOM automatisch als erstes Zeichen geschrieben und beim Lesen der Datei stillschweigend verworfen. Es gibt Varianten dieser Kodierungen, etwa „utf-16-le“ und „utf-16-be“ für Little-Endian- und Big-Endian-Kodierungen, die eine bestimmte Byte-Reihenfolge festlegen und die BOM nicht überspringen.
In manchen Bereichen ist es außerdem Konvention, am Anfang UTF-8-kodierter Dateien eine „BOM“ zu verwenden. Der Name führt in die Irre, weil UTF-8 nicht von der Byte-Reihenfolge abhängt. Die Markierung kündigt lediglich an, dass die Datei in UTF-8 kodiert ist. Um solche Dateien zu lesen, verwende den Codec „utf-8-sig“, der die Markierung, falls vorhanden, automatisch überspringt.
Unicode-Dateinamen¶
Die meisten heute verbreiteten Betriebssysteme unterstützen Dateinamen, die beliebige Unicode-Zeichen enthalten. Üblicherweise wird das umgesetzt, indem die Unicode-Zeichenkette in eine Kodierung umgewandelt wird, die je nach System unterschiedlich ist. Heute nähert sich Python der Verwendung von UTF-8 an: Python unter macOS verwendet seit mehreren Versionen UTF-8, und Python 3.6 ist auch unter Windows auf UTF-8 umgestiegen. Auf Unix-Systemen gibt es nur dann eine Dateisystem-Kodierung, wenn du die Umgebungsvariablen LANG oder LC_CTYPE gesetzt hast, wenn nicht, ist die Standardkodierung wieder UTF-8.
Die Funktion sys.getfilesystemencoding() gibt die Kodierung zurück, die auf deinem aktuellen System zu verwenden ist, falls du von Hand kodieren willst – es gibt aber nicht viel Grund, sich die Mühe zu machen. Wenn du eine Datei zum Lesen oder Schreiben öffnest, kannst du üblicherweise einfach die Unicode-Zeichenkette als Dateinamen übergeben, und sie wird automatisch in die richtige Kodierung umgewandelt:
filename = 'filename\u4500abc'
with open(filename, 'w') as f:
f.write('blah\n')
Funktionen im Modul os wie os.stat() nehmen ebenfalls Unicode-Dateinamen an.
Die Funktion os.listdir() gibt Dateinamen zurück, was eine Frage aufwirft: Soll sie die Unicode-Fassung der Dateinamen zurückgeben oder Bytes, die die kodierten Fassungen enthalten? os.listdir() kann beides, je nachdem, ob du den Verzeichnispfad als Bytes oder als Unicode-Zeichenkette übergeben hast. Übergibst du eine Unicode-Zeichenkette als Pfad, werden die Dateinamen mit der Kodierung des Dateisystems dekodiert und eine Liste von Unicode-Zeichenketten zurückgegeben. Übergibst du einen Bytepfad, kommen die Dateinamen als Bytes zurück. Nehmen wir zum Beispiel an, die Standard-Dateisystem-Kodierung ist UTF-8. Dann erzeugt das folgende Programm:
fn = 'filename\u4500abc'
f = open(fn, 'w')
f.close()
import os
print(os.listdir(b'.'))
print(os.listdir('.'))
die folgende Ausgabe:
$ python listdir-test.py
[b'filename\xe4\x94\x80abc', ...]
['filename\u4500abc', ...]
Die erste Liste enthält UTF-8-kodierte Dateinamen, die zweite Liste die Unicode-Fassungen.
Beachte, dass du bei diesen APIs in den meisten Fällen einfach bei Unicode bleiben kannst. Die byte-basierten APIs sollten nur auf Systemen verwendet werden, auf denen nicht dekodierbare Dateinamen vorkommen können. Das sind heute so gut wie nur noch Unix-Systeme.
Hinweise zum Schreiben Unicode-fähiger Programme¶
Dieser Abschnitt gibt einige Vorschläge zum Schreiben von Software, die mit Unicode umgeht.
Der wichtigste Hinweis ist:
Software sollte intern nur mit Unicode-Zeichenketten arbeiten, die Eingabedaten so früh wie möglich dekodieren und die Ausgabe erst am Ende kodieren.
Wenn du versuchst, Verarbeitungsfunktionen zu schreiben, die sowohl Unicode- als auch Byte-Zeichenketten annehmen, wird dein Programm überall dort fehleranfällig, wo du die beiden verschiedenen Arten von Zeichenketten verbindest. Es gibt kein automatisches Kodieren oder Dekodieren: Machst du zum Beispiel str + bytes, wird ein TypeError ausgelöst.
Bei Daten, die von einem Webbrowser oder einer anderen nicht vertrauenswürdigen Quelle kommen, ist es eine verbreitete Technik, eine Zeichenkette auf unzulässige Zeichen zu prüfen, bevor sie in einer erzeugten Befehlszeile verwendet oder in einer Datenbank gespeichert wird. Wenn du das tust, achte darauf, die dekodierte Zeichenkette zu prüfen und nicht die kodierten Bytedaten. Manche Kodierungen haben interessante Eigenschaften, etwa nicht bijektiv oder nicht vollständig ASCII-verträglich zu sein. Das gilt besonders dann, wenn die Eingabedaten auch die Kodierung angeben, weil der Angreifer dann einen raffinierten Weg wählen kann, bösartigen Text im kodierten Bytestrom zu verstecken.
Zwischen Dateikodierungen umwandeln¶
Die Klasse StreamRecoder kann transparent zwischen Kodierungen umwandeln: Sie nimmt einen Strom, der Daten in Kodierung 1 liefert, und verhält sich wie ein Strom, der Daten in Kodierung 2 liefert.
Wenn du zum Beispiel eine Eingabedatei f in Latin-1 hast, kannst du sie mit einem StreamRecoder umhüllen, damit UTF-8-kodierte Bytes zurückkommen:
new_f = codecs.StreamRecoder(f,
# en/decoder: used by read() to encode its results and
# by write() to decode its input.
codecs.getencoder('utf-8'), codecs.getdecoder('utf-8'),
# reader/writer: used to read and write to the stream.
codecs.getreader('latin-1'), codecs.getwriter('latin-1') )
Dateien in unbekannter Kodierung¶
Was kannst du tun, wenn du eine Datei ändern musst, aber deren Kodierung nicht kennst? Wenn du weißt, dass die Kodierung ASCII-verträglich ist, und nur die ASCII-Teile untersuchen oder verändern willst, kannst du die Datei mit der Fehlerbehandlung surrogateescape öffnen:
with open(fname, 'r', encoding="ascii", errors="surrogateescape") as f:
data = f.read()
# make changes to the string 'data'
with open(fname + '.new', 'w',
encoding="ascii", errors="surrogateescape") as f:
f.write(data)
Die Fehlerbehandlung surrogateescape dekodiert alle Nicht-ASCII-Bytes als Codepunkte in einem besonderen Bereich von U+DC80 bis U+DCFF. Diese Codepunkte werden wieder zu denselben Bytes, wenn die Fehlerbehandlung surrogateescape zum Kodieren der Daten und zum Zurückschreiben verwendet wird.
Quellen¶
Ein Abschnitt von Mastering Python 3 Input/Output, einem Vortrag von David Beazley auf der PyCon 2010, behandelt Textverarbeitung und den Umgang mit Binärdaten.
Die PDF-Folien zu Marc-André Lemburgs Vortrag „Writing Unicode-aware Applications in Python“ behandeln Fragen der Zeichenkodierung sowie die Internationalisierung und Lokalisierung einer Anwendung. Diese Folien behandeln nur Python 2.x.
The Guts of Unicode in Python ist ein Vortrag von Benjamin Peterson auf der PyCon 2013, der die interne Unicode-Darstellung in Python 3.3 behandelt.
Danksagungen¶
Der erste Entwurf dieses Dokuments wurde von Andrew Kuchling geschrieben. Seither wurde es von Alexander Belopolsky, Georg Brandl, Andrew Kuchling und Ezio Melotti weiter überarbeitet.
Dank an die folgenden Personen, die Fehler bemerkt oder Vorschläge zu diesem Artikel gemacht haben: Éric Araujo, Nicholas Bastin, Nick Coghlan, Marius Gedminas, Kent Johnson, Ken Krugler, Marc-André Lemburg, Martin von Löwis, Terry J. Reedy, Serhiy Storchaka, Eryk Sun, Chad Whitacre, Graham Wideman.