Unicode HOWTO

Version:

1.12

Dieses HOWTO diskutiert Pythons Unterstützung für die Unicode-Spezifikation zur Darstellung von Textdaten und erklärt verschiedene Probleme, die Menschen häufig bei der Arbeit mit Unicode antreffen.

Einführung in Unicode

Definitionen

Heutige Programme müssen eine Vielzahl von Zeichen verarbeiten können. Anwendungen werden oft internationalisiert, um Nachrichten und Ausgaben in einer Vielzahl von benutzerwählbaren Sprachen anzuzeigen; dasselbe Programm muss möglicherweise eine Fehlermeldung in Englisch, Französisch, Japanisch, Hebräisch oder Russisch ausgeben. Webinhalte können in jeder dieser Sprachen verfasst sein und auch eine Vielzahl von Emoji-Symbolen enthalten. Pythons String-Typ verwendet den Unicode-Standard zur Darstellung von Zeichen, was es Python-Programmen ermöglicht, mit all diesen verschiedenen möglichen Zeichen zu arbeiten.

Unicode (https://www.unicode.org/) ist eine Spezifikation, die darauf abzielt, jedes von menschlichen Sprachen verwendete Zeichen aufzulisten und jedem Zeichen seinen eigenen eindeutigen Code zu geben. Die Unicode-Spezifikationen werden kontinuierlich überarbeitet und aktualisiert, um neue Sprachen und Symbole hinzuzufügen.

Ein Zeichen ist die kleinste mögliche Komponente eines Textes. 'A', 'B', 'C' usw. sind alles verschiedene Zeichen. Ebenso sind es 'È' und 'Í'. Zeichen variieren je nach Sprache oder Kontext, über den Sie sprechen. Zum Beispiel gibt es ein Zeichen für "Römisches Ziffernzeichen Eins", 'Ⅰ', das sich vom Großbuchstaben 'I' unterscheidet. Sie werden normalerweise gleich aussehen, aber dies sind zwei verschiedene Zeichen mit unterschiedlichen Bedeutungen.

Der Unicode-Standard beschreibt, wie Zeichen durch Codepunkte dargestellt werden. Ein Codepunktwert ist eine Ganzzahl im Bereich von 0 bis 0x10FFFF (ungefähr 1,1 Millionen Werte; die tatsächlich zugewiesene Anzahl ist geringer). Im Standard und in diesem Dokument wird ein Codepunkt mit der Notation U+265E geschrieben, um das Zeichen mit dem Wert 0x265e (9.822 dezimal) zu bedeuten.

Der Unicode-Standard enthält viele Tabellen, die Zeichen und ihre entsprechenden 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 implizieren diese Definitionen, dass es bedeutungslos ist zu sagen 'dies ist Zeichen U+265E'. U+265E ist ein Codepunkt, der ein bestimmtes Zeichen darstellt; in diesem Fall repräsentiert er das Zeichen 'SCHWARZER SCHACHKNECHT', '♞'. In informellen Kontexten wird diese Unterscheidung zwischen Codepunkten und Zeichen manchmal vergessen.

Ein Zeichen wird auf einem Bildschirm oder auf Papier durch eine Menge grafischer Elemente dargestellt, die als Glyphe bezeichnet wird. Die Glyphe für ein Großbuchstaben A besteht beispielsweise aus zwei diagonalen Strichen und einem horizontalen Strich, wobei die genauen Details von der verwendeten Schriftart abhängen. Die meisten Python-Codes müssen sich keine Gedanken über Glyphen machen; die Ermittlung der korrekten anzuzeigenden Glyphe ist im Allgemeinen die Aufgabe eines GUI-Toolkits oder des Font-Renderers eines Terminals.

Kodierungen

Um den vorherigen Abschnitt zusammenzufassen: Ein Unicode-String ist eine Sequenz von Codepunkten, die Zahlen von 0 bis 0x10FFFF (1.114.111 dezimal) sind. Diese Sequenz von Codepunkten muss im Speicher als eine Menge von Code-Einheiten dargestellt werden, und Code-Einheiten werden dann auf 8-Bit-Bytes abgebildet. Die Regeln für die Übersetzung eines Unicode-Strings in eine Byte-Sequenz werden als Zeichenkodierung oder einfach als Kodierung bezeichnet.

Die erste Kodierung, an die Sie vielleicht denken, ist die Verwendung von 32-Bit-Ganzzahlen als Code-Einheit und dann die Darstellung von 32-Bit-Ganzzahlen der CPU. In dieser Darstellung könnte der String "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, aber ihre Verwendung birgt eine Reihe von Problemen.

  1. Sie ist nicht portabel; verschiedene Prozessoren ordnen die Bytes unterschiedlich an.

  2. Sie ist sehr platzraubend. In den meisten Texten sind die meisten Codepunkte kleiner als 127 oder kleiner als 255, sodass viel Platz von 0x00-Bytes eingenommen wird. Der obige String benötigt 24 Bytes im Vergleich zu den 6 Bytes, die für eine ASCII-Darstellung benötigt werden. Ein erhöhter RAM-Verbrauch ist nicht allzu schlimm (Desktop-Computer haben Gigabyte RAM und Strings sind normalerweise nicht so groß), aber die Vervierfachung unserer RAM-Nutzung von Festplatte und Netzwerkbandbreite ist unerträglich.

  3. Sie ist nicht kompatibel mit bestehenden C-Funktionen wie strlen(), sodass eine neue Familie von Wide-String-Funktionen verwendet werden müsste.

Daher wird diese Kodierung nicht sehr häufig verwendet, und die Leute wählen stattdessen andere Kodierungen, die effizienter und praktischer sind, wie z. B. 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 bei der Kodierung 8-Bit-Werte verwendet werden. (Es gibt auch UTF-16- und UTF-32-Kodierungen, aber diese werden seltener als UTF-8 verwendet.) UTF-8 verwendet die folgenden Regeln

  1. Wenn der Codepunkt < 128 ist, wird er durch den entsprechenden Byte-Wert dargestellt.

  2. Wenn der Codepunkt >= 128 ist, wird er in eine Sequenz von zwei, drei oder vier Bytes umgewandelt, wobei jedes Byte der Sequenz zwischen 128 und 255 liegt.

UTF-8 hat mehrere praktische Vorteile

  1. Es kann jeden Unicode-Codepunkt verarbeiten.

  2. Ein Unicode-String wird in eine Byte-Sequenz umgewandelt, die nur dann eingebettete Null-Bytes enthält, wenn sie das Nullzeichen (U+0000) darstellen. Das bedeutet, dass UTF-8-Strings von C-Funktionen wie strcpy() verarbeitet und über Protokolle gesendet werden können, die keine Null-Bytes für etwas anderes als End-of-String-Markierungen behandeln können.

  3. Ein ASCII-Textstring ist ebenfalls gültiger UTF-8-Text.

  4. UTF-8 ist relativ kompakt; die Mehrheit der gebräuchlichsten Zeichen kann mit einem oder zwei Bytes dargestellt werden.

  5. Wenn Bytes beschädigt oder verloren gehen, ist es möglich, den Beginn des nächsten UTF-8-kodierten Codepunkts zu bestimmen und zu resynchronisieren. Es ist auch unwahrscheinlich, dass zufällige 8-Bit-Daten wie gültiges UTF-8 aussehen.

  6. UTF-8 ist eine byteorientierte Kodierung. Die Kodierung besagt, dass jedes Zeichen durch eine spezifische Sequenz von einem oder mehreren Bytes dargestellt wird. Dies vermeidet Byte-Reihenfolge-Probleme, die bei Integer- und Wortorientierten Kodierungen wie UTF-16 und UTF-32 auftreten können, bei denen die Byte-Sequenz je nach Hardware, auf der der String kodiert wurde, variiert.

Referenzen

Die Website des Unicode-Konsortiums enthält Zeichenübersichten, ein Glossar und PDF-Versionen der Unicode-Spezifikation. Seien Sie auf schwierige Lektüre vorbereitet. Eine Chronologie des Ursprungs und der Entwicklung von Unicode ist ebenfalls auf der Website verfügbar.

Auf dem Computerphile Youtube-Kanal bespricht Tom Scott kurz die Geschichte von Unicode und UTF-8 (9 Minuten 36 Sekunden).

Um den Standard besser zu verstehen, hat Jukka Korpela einen einführenden Leitfaden zum Lesen der Unicode-Zeichentabellen geschrieben.

Ein weiterer guter einführender Artikel wurde von Joel Spolsky verfasst. Wenn diese Einführung für Sie nicht klar war, sollten Sie diesen alternativen Artikel lesen, bevor Sie fortfahren.

Wikipedia-Einträge sind oft hilfreich; siehe zum Beispiel die Einträge für "Zeichenkodierung" und UTF-8.

Pythons Unicode-Unterstützung

Nachdem Sie die Grundlagen von Unicode kennengelernt haben, können wir uns nun Pythons Unicode-Funktionen ansehen.

Der String-Typ

Seit Python 3.0 enthält der str-Typ der Sprache Unicode-Zeichen, was bedeutet, dass jeder String, der mit "unicode rocks!", 'unicode rocks!' oder der dreifach zitierten String-Syntax erstellt wird, als Unicode gespeichert wird.

Die Standardkodierung für Python-Quellcode ist UTF-8, sodass Sie einfach ein Unicode-Zeichen in ein String-Literal einfügen können

try:
    with open('/tmp/input.txt', 'r') as f:
        ...
except OSError:
    # 'File not found' error message.
    print("Fichier non trouvé")

Seitennotiz: Python 3 unterstützt auch die Verwendung von Unicode-Zeichen in Bezeichnern.

répertoire = "/tmp/records.log"
with open(répertoire, "w") as f:
    f.write("test\n")

Wenn Sie ein bestimmtes Zeichen nicht in Ihren Editor eingeben können oder den Quellcode aus irgendeinem Grund ASCII-rein halten möchten, können Sie auch Escape-Sequenzen in String-Literalen verwenden. (Abhängig von Ihrem System sehen Sie möglicherweise die tatsächliche Großbuchstaben-Delta-Glyphe anstelle eines u-Escapes.)

>>> "\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'

Darüber hinaus kann ein String mit der decode()-Methode von bytes erstellt werden. Diese Methode nimmt ein *encoding*-Argument, wie z. B. UTF-8, und optional ein *errors*-Argument.

Das *errors*-Argument gibt die Reaktion an, wenn der Eingabestring gemäß den Regeln der Kodierung nicht konvertiert 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 einfach aus dem Unicode-Ergebnis 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 Strings angegeben, die den Namen der Kodierung enthalten. Python verfügt über etwa 100 verschiedene Kodierungen; eine Liste finden Sie in der Python-Bibliotheksreferenz unter Standard-Kodierungen. Einige Kodierungen haben mehrere Namen; zum Beispiel sind 'latin-1', 'iso_8859_1' und '8859' alles Synonyme für dieselbe Kodierung.

Einstellige Unicode-Strings können auch mit der integrierten Funktion chr() erstellt werden, die Ganzzahlen nimmt und einen Unicode-String der Länge 1 zurückgibt, der den entsprechenden Codepunkt enthält. Die umgekehrte Operation ist die integrierte Funktion ord(), die einen einstelliges Unicode-String nimmt und den Codepunktwert zurückgibt.

>>> chr(57344)
'\ue000'
>>> ord('\ue000')
57344

Konvertierung in Bytes

Die umgekehrte Methode zu bytes.decode() ist str.encode(), die eine bytes-Darstellung des Unicode-Strings zurückgibt, der in der angeforderten *encoding* kodiert ist.

Der *errors*-Parameter ist derselbe wie der Parameter der decode()-Methode, unterstützt aber einige zusätzliche Handler. Neben 'strict', 'ignore' und 'replace' (was in diesem Fall ein Fragezeichen anstelle des nicht kodierbaren Zeichens einfügt), gibt es auch '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'&#40960;abcd&#1972;'
>>> u.encode('ascii', 'backslashreplace')
b'\\ua000abcd\\u07b4'
>>> u.encode('ascii', 'namereplace')
b'\\N{YI SYLLABLE IT}abcd\\u07b4'

Die Low-Level-Routinen zum Registrieren und Zugreifen auf die verfügbaren Kodierungen befinden sich im Modul codecs. Das Implementieren neuer Kodierungen erfordert auch das Verständnis des Moduls codecs. Die von diesem Modul zurückgegebenen Kodierungs- und Dekodierungsfunktionen sind jedoch normalerweise niedriger als komfortabel, und das Schreiben neuer Kodierungen ist eine spezialisierte Aufgabe, daher wird das Modul in diesem HOWTO nicht behandelt.

Unicode-Literale im Python-Quellcode

Im Python-Quellcode können spezifische Unicode-Codepunkte mit der Escape-Sequenz \u geschrieben werden, gefolgt von vier Hexadezimalziffern, die den Codepunkt angeben. Die Escape-Sequenz \U ist ähnlich, erwartet aber acht Hexadezimalziffern, 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]

Die Verwendung von Escape-Sequenzen für Codepunkte größer als 127 ist in kleinen Dosen in Ordnung, wird aber zu einer Belästigung, wenn Sie viele akzentuierte Zeichen verwenden, wie Sie es in einem Programm mit Nachrichten in französischer oder einer anderen akzentnutzenden Sprache tun würden. Sie können auch Strings mit der integrierten Funktion chr() zusammensetzen, aber das ist noch mühsamer.

Idealerweise möchten Sie Literale in der natürlichen Kodierung Ihrer Sprache schreiben können. Sie könnten dann Python-Quellcode mit Ihrem bevorzugten Editor bearbeiten, der akzentuierte Zeichen natürlich anzeigt und zur Laufzeit die richtigen Zeichen verwendet.

Python unterstützt standardmäßig das Schreiben von Quellcode in UTF-8, aber Sie können fast jede Kodierung verwenden, wenn Sie die verwendete Kodierung deklarieren. Dies geschieht durch Einfügen eines speziellen Kommentars als erste oder zweite Zeile der Quelldatei.

#!/usr/bin/env python
# -*- coding: latin-1 -*-

u = 'abcdé'
print(ord(u[-1]))

Die Syntax ist inspiriert von Emacs' Notation zur Angabe von Variablen, die lokal für eine Datei sind. Emacs unterstützt viele verschiedene Variablen, aber Python unterstützt nur 'coding'. Die Symbole -*- zeigen Emacs an, dass der Kommentar speziell ist; sie haben keine Bedeutung für Python, sind aber eine Konvention. Python sucht nach coding: name oder coding=name im Kommentar.

Wenn Sie einen solchen Kommentar nicht einfügen, wird die standardmäßig verwendete Kodierung UTF-8 sein, wie bereits erwähnt. Siehe auch PEP 263 für weitere Informationen.

Unicode-Eigenschaften

Die Unicode-Spezifikation enthält eine Datenbank mit Informationen über Codepunkte. Für jeden definierten Codepunkt enthält die Information den Namen des Zeichens, seine Kategorie, den numerischen Wert, falls zutreffend (für Zeichen, die numerische Konzepte wie römische Ziffern, Brüche wie ein Drittel und vier Fünftel usw. darstellen). Es gibt auch anzeigebezogene Eigenschaften, z. B. wie der Codepunkt in bidirektionalem Text verwendet wird.

Das folgende Programm zeigt einige Informationen über mehrere Zeichen und gibt den numerischen Wert 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]))

Beim Ausführen gibt dies 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 Kategorien sind Abkürzungen, die die Art des Zeichens beschreiben. Diese sind in Kategorien wie "Buchstabe", "Zahl", "Satzzeichen" oder "Symbol" gruppiert, die wiederum in Unterkategorien unterteilt sind. Um die Codes aus der obigen Ausgabe zu nehmen, bedeutet 'Ll' 'Buchstabe, Kleinbuchstabe', 'No' bedeutet "Zahl, Sonstige", 'Mn' ist "Markierung, nicht-breitstellend" und 'So' ist "Symbol, Sonstige". Siehe den Abschnitt Allgemeine Kategoriewerte der Dokumentation der Unicode-Zeichendatenbank für eine Liste von Kategorien-Codes.

Vergleich von Strings

Unicode fügt dem Vergleich von Strings einige Komplikationen hinzu, da dieselbe Zeichenmenge durch unterschiedliche Sequenzen von Codepunkten dargestellt werden kann. Beispielsweise kann ein Buchstabe wie 'ê' als einzelner Codepunkt U+00EA oder als U+0065 U+0302 dargestellt werden, was der Codepunkt für 'e' gefolgt von einem Codepunkt für 'Kombinierender Zirkumflex' ist. Diese erzeugen beim Drucken die gleiche Ausgabe, aber der eine ist ein String der Länge 1 und der andere der Länge 2.

Ein Werkzeug für einen vergleichsunabhängigen Vergleich ist die String-Methode casefold(), die einen String gemäß einem vom Unicode-Standard beschriebenen Algorithmus in eine Groß-/Kleinschreibung ignorierende Form umwandelt. Dieser Algorithmus hat eine spezielle Behandlung für Zeichen wie den deutschen Buchstaben 'ß' (Codepunkt U+00DF), der zum Paar der Kleinbuchstaben 'ss' wird.

>>> street = 'Gürzenichstraße'
>>> street.casefold()
'gürzenichstrasse'

Ein zweites Werkzeug ist die Funktion normalize() des Moduls unicodedata, die Strings in eine von mehreren Normalformen umwandelt, bei der Buchstaben gefolgt von einem kombinierenden Zeichen durch einzelne Zeichen ersetzt werden. normalize() kann verwendet werden, um String-Vergleiche durchzuführen, die nicht fälschlicherweise Ungleichheit melden, wenn zwei Strings 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))

Beim Ausführen gibt dies aus:

$ python compare-strs.py
length of first string= 1
length of second string= 2
True

Das erste Argument der Funktion normalize() ist ein String, der die gewünschte Normalisierungsform angibt, die entweder 'NFC', 'NFKC', 'NFD' oder 'NFKD' sein kann.

Der Unicode-Standard legt auch fest, wie vergleichsunabhängige Vergleiche durchgeführt werden.

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))

Dies gibt True aus. (Warum wird NFD() zweimal aufgerufen? Weil es einige Zeichen gibt, die dazu führen, dass casefold() einen nicht normalisierten String zurückgibt, sodass das Ergebnis erneut normalisiert werden muss. Siehe Abschnitt 3.13 des Unicode-Standards für eine Diskussion und ein Beispiel.)

Unicode-Reguläre Ausdrücke

Die vom Modul re unterstützten regulären Ausdrücke können entweder als Bytes oder als Strings angegeben werden. Einige der speziellen Zeichensequenzen wie \d und \w haben unterschiedliche Bedeutungen, je nachdem, ob das Muster als Bytes oder als String bereitgestellt wird. Zum Beispiel passt \d zu den Zeichen [0-9] in Bytes, passt aber in Strings zu jedem Zeichen, das in der Kategorie 'Nd' steht.

Der String in diesem Beispiel hat die Zahl 57 in thailändischen und arabischen Ziffern

import re
p = re.compile(r'\d+')

s = "Over \u0e55\u0e57 57 flavours"
m = p.search(s)
print(repr(m.group()))

Bei der Ausführung passt \d+ zu den thailändischen Ziffern und gibt sie aus. Wenn Sie das Flag re.ASCII an compile() übergeben, passt \d+ stattdessen zum Teilstring "57".

Ebenso passt \w zu einer Vielzahl von Unicode-Zeichen, aber nur zu [a-zA-Z0-9_] in Bytes oder wenn re.ASCII bereitgestellt wird, und \s passt zu Unicode-Leerzeichen oder zu [ \t\n\r\f\v].

Referenzen

Einige gute alternative Diskussionen über Pythons Unicode-Unterstützung sind:

Der str-Typ wird in der Python-Bibliotheksreferenz unter Textsequenztyp — str beschrieben.

Die Dokumentation für das Modul unicodedata.

Die Dokumentation für das Modul codecs.

Marc-André Lemburg hielt einen Vortrag mit dem Titel „Python and Unicode“ (PDF-Folien) auf der EuroPython 2002. Die Folien sind ein ausgezeichneter Überblick über das Design von Pythons Unicode-Funktionen (wo der Unicode-String-Typ unicode heißt und Literale mit u beginnen).

Lesen und Schreiben von Unicode-Daten

Nachdem Sie Code geschrieben haben, der mit Unicode-Daten arbeitet, ist das nächste Problem die Ein-/Ausgabe. Wie bekommen Sie Unicode-Strings in Ihr Programm und wie konvertieren Sie Unicode in eine Form, die für die Speicherung oder Übertragung geeignet ist?

Es ist möglich, dass Sie nichts tun müssen, abhängig von Ihren Eingabequellen und Ausgabezielen. Sie sollten prüfen, ob die von Ihrer Anwendung verwendeten Bibliotheken Unicode nativ unterstützen. XML-Parser geben beispielsweise oft Unicode-Daten zurück. Viele relationale Datenbanken unterstützen auch Spalten mit Unicode-Werten und können Unicode-Werte aus einer SQL-Abfrage zurückgeben.

Unicode-Daten werden normalerweise in eine bestimmte Kodierung umgewandelt, bevor sie auf die Festplatte geschrieben oder über eine Socket gesendet werden. Es ist möglich, die gesamte Arbeit selbst zu erledigen: Eine Datei öffnen, ein 8-Bit-Bytes-Objekt daraus lesen und die Bytes mit bytes.decode(encoding) konvertieren. Der manuelle Ansatz wird jedoch nicht empfohlen.

Ein Problem ist die Multibyte-Natur von Kodierungen; ein Unicode-Zeichen kann durch mehrere Bytes dargestellt werden. Wenn Sie die Datei in Blöcken beliebiger Größe lesen möchten (z. B. 1024 oder 4096 Bytes), müssen Sie Code zur Fehlerbehandlung schreiben, um den Fall abzufangen, dass nur ein Teil der Bytes, die ein einzelnes Unicode-Zeichen kodieren, am Ende eines Blocks gelesen wird. Eine Lösung wäre, die gesamte Datei in den Speicher zu lesen und dann die Dekodierung durchzuführen, aber das hindert Sie daran, mit extrem großen Dateien zu arbeiten; wenn Sie eine 2-GiB-Datei lesen müssen, benötigen Sie 2 GiB RAM. (Eigentlich mehr, da Sie für eine Weile sowohl den kodierten String als auch seine Unicode-Version im Speicher haben müssten.)

Die Lösung wäre, die Low-Level-Dekodierungsschnittstelle zu verwenden, um den Fall von teilweisen Kodierungssequenzen abzufangen. Die Arbeit an der Implementierung dieser wurde bereits für Sie erledigt: Die integrierte Funktion open() kann ein Dateiein-ähnliches Objekt zurückgeben, das davon ausgeht, dass der Inhalt der Datei in einer angegebenen Kodierung vorliegt und Unicode-Parameter für Methoden wie read() und write() akzeptiert. Dies geschieht über die Parameter *encoding* und *errors* von open(), die genauso interpretiert werden wie die in str.encode() und bytes.decode().

Das Lesen von Unicode aus einer Datei 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 Update-Modus zu öffnen, was sowohl das Lesen als auch das Schreiben ermöglicht.

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-Order-Mark (BOM) verwendet und oft als erstes Zeichen einer Datei geschrieben, um die automatische Erkennung der Byte-Reihenfolge der Datei zu unterstützen. Einige Kodierungen, wie z. B. UTF-16, erwarten, dass eine BOM am Anfang einer Datei vorhanden ist. Wenn eine solche Kodierung verwendet wird, wird die BOM automatisch als erstes Zeichen geschrieben und beim Lesen der Datei stillschweigend entfernt. Es gibt Varianten dieser Kodierungen, wie 'utf-16-le' und 'utf-16-be' für Little-Endian- und Big-Endian-Kodierungen, die eine bestimmte Byte-Reihenfolge angeben und die BOM nicht überspringen.

In einigen Bereichen ist es auch Konvention, am Anfang von UTF-8-kodierten Dateien eine "BOM" zu verwenden. Der Name ist irreführend, da UTF-8 nicht von der Byte-Reihenfolge abhängt. Die Markierung kündigt lediglich an, dass die Datei in UTF-8 kodiert ist. Zum Lesen solcher Dateien verwenden Sie den 'utf-8-sig'-Codec, um die Markierung automatisch zu überspringen, falls vorhanden.

Unicode-Dateinamen

Die meisten heute gängigen Betriebssysteme unterstützen Dateinamen, die beliebige Unicode-Zeichen enthalten. Dies wird normalerweise durch die Konvertierung des Unicode-Strings in eine Kodierung implementiert, die je nach System variiert. Heute nähert sich Python der Verwendung von UTF-8 an: Python unter MacOS verwendet UTF-8 seit mehreren Versionen, und Python 3.6 hat auch unter Windows auf UTF-8 umgestellt. Auf Unix-Systemen gibt es nur eine Dateisystemkodierung, wenn Sie die Umgebungsvariablen LANG oder LC_CTYPE gesetzt haben. Wenn nicht, ist die Standardkodierung wieder UTF-8.

Die Funktion sys.getfilesystemencoding() gibt die auf Ihrem aktuellen System zu verwendende Kodierung zurück, falls Sie die Kodierung manuell vornehmen möchten. Aber es gibt kaum einen Grund dafür. Beim Öffnen einer Datei zum Lesen oder Schreiben können Sie normalerweise einfach den Unicode-String als Dateinamen angeben, und er wird automatisch für Sie in die richtige Kodierung konvertiert.

filename = 'filename\u4500abc'
with open(filename, 'w') as f:
    f.write('blah\n')

Funktionen im Modul os wie os.stat() akzeptieren ebenfalls Unicode-Dateinamen.

Die Funktion os.listdir() gibt Dateinamen zurück, was eine Frage aufwirft: Soll sie die Unicode-Version von Dateinamen zurückgeben oder Bytes mit den kodierten Versionen? os.listdir() kann beides tun, je nachdem, ob Sie den Verzeichnispfad als Bytes oder als Unicode-String angegeben haben. Wenn Sie einen Unicode-String als Pfad übergeben, werden die Dateinamen mit der Kodierung des Dateisystems dekodiert und eine Liste von Unicode-Strings zurückgegeben, während die Übergabe eines Byte-Pfades die Dateinamen als Bytes zurückgibt. Angenommen, die Standard-Dateisystemkodierung ist UTF-8, führt die Ausführung des folgenden Programms

fn = 'filename\u4500abc'
f = open(fn, 'w')
f.close()

import os
print(os.listdir(b'.'))
print(os.listdir('.'))

zu folgender Ausgabe

$ python listdir-test.py
[b'filename\xe4\x94\x80abc', ...]
['filename\u4500abc', ...]

Die erste Liste enthält UTF-8-kodierte Dateinamen, und die zweite Liste enthält die Unicode-Versionen.

Beachten Sie, dass Sie in den meisten Fällen einfach bei der Verwendung von Unicode mit diesen APIs bleiben sollten. Die Byte-APIs sollten nur auf Systemen verwendet werden, auf denen nicht dekodierbare Dateinamen vorhanden sein können; das sind heute praktisch nur noch Unix-Systeme.

Tipps für das Schreiben von Unicode-fähigen Programmen

Dieser Abschnitt enthält einige Vorschläge zum Schreiben von Software, die mit Unicode arbeitet.

Der wichtigste Tipp ist

Software sollte intern nur mit Unicode-Strings arbeiten, Eingabedaten so bald wie möglich dekodieren und die Ausgabe erst am Ende kodieren.

Wenn Sie versuchen, Verarbeitungsfunktionen zu schreiben, die sowohl Unicode- als auch Byte-Strings akzeptieren, werden Sie feststellen, dass Ihr Programm anfällig für Fehler ist, wann immer Sie die beiden verschiedenen Arten von Strings kombinieren. Es gibt keine automatische Kodierung oder Dekodierung: Wenn Sie z. B. str + bytes ausführen, wird ein TypeError ausgelöst.

Bei der Verwendung von Daten, die von einem Webbrowser oder einer anderen nicht vertrauenswürdigen Quelle stammen, ist eine gängige Technik, auf illegale Zeichen in einem String zu prüfen, bevor der String in einer generierten Befehlszeile verwendet oder in einer Datenbank gespeichert wird. Wenn Sie dies tun, stellen Sie sicher, dass Sie den dekodierten String und nicht die kodierten Byte-Daten prüfen; einige Kodierungen können interessante Eigenschaften haben, wie z. B. nicht biunivok zu sein oder nicht vollständig ASCII-kompatibel zu sein. Dies gilt insbesondere dann, wenn die Eingabedaten auch die Kodierung angeben, da der Angreifer dann einen cleveren Weg wählen kann, um bösartigen Text im kodierten Bytestream zu verstecken.

Konvertierung zwischen Dateikodierungen

Die Klasse StreamRecoder kann transparent zwischen Kodierungen konvertieren, wobei ein Stream, der Daten in Kodierung #1 zurückgibt, übernommen und wie ein Stream behandelt wird, der Daten in Kodierung #2 zurückgibt.

Wenn Sie beispielsweise eine Eingabedatei f haben, die in Latin-1 vorliegt, können Sie sie mit einem StreamRecoder umschließen, um Bytes zurückzugeben, die in UTF-8 kodiert sind

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 können Sie tun, wenn Sie eine Änderung an einer Datei vornehmen müssen, aber die Kodierung der Datei nicht kennen? Wenn Sie wissen, dass die Kodierung ASCII-kompatibel ist und Sie nur die ASCII-Teile untersuchen oder ändern möchten, können Sie die Datei mit dem Fehlerbehandlungsmechanismus 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)

Der Fehlerbehandlungsmechanismus surrogateescape dekodiert alle Nicht-ASCII-Bytes als Code-Punkte in einem speziellen Bereich von U+DC80 bis U+DCFF. Diese Code-Punkte werden dann wieder in dieselben Bytes umgewandelt, wenn der Fehlerbehandlungsmechanismus surrogateescape verwendet wird, um die Daten zu kodieren und zurückzuschreiben.

Referenzen

Ein Abschnitt von Mastering Python 3 Input/Output, ein PyCon 2010 Vortrag von David Beazley, behandelt Textverarbeitung und die Handhabung binärer Daten.

Die PDF-Folien von Marc-André Lemburgs Präsentation "Writing Unicode-aware Applications in Python" diskutieren 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 PyCon 2013 Vortrag von Benjamin Peterson, der die interne Unicode-Darstellung in Python 3.3 behandelt.

Danksagungen

Der erste Entwurf dieses Dokuments wurde von Andrew Kuchling verfasst. Seitdem wurde es von Alexander Belopolsky, Georg Brandl, Andrew Kuchling und Ezio Melotti weiter überarbeitet.

Vielen 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.