SlideShare une entreprise Scribd logo
1  sur  92
Télécharger pour lire hors ligne
Erste Hilfekasten
für Unicode mit Python
Version 1.2
Thomas Aglassinger
https://github.com/roskakori/talks/tree/master/pygraz/unicode
Agenda
1.Wozu Unicode?
2.Unicode Zeichenketten in Python 3
3.Encodings
4.Wann Daten (de-)kodieren?
5.Vorhandenes Encoding ermitteln
6.UnicodeError
7.Unicode Zeichenketten in Python 2
Ziele
Die Teilnehmer können...
● ...Texte mit Umlauten in Python verarbeiten.
● ...ein geeignetes Encoding wählen.
● ...die Stärken und Schwächen der wichtigsten
Encodings zuordnen.
● ...UnicodeError vermeiden.
Nicht-Themen
● Verstehen der Encodings auf Bit-Ebene
→ Wikipedia
● Implementieren eigener Encodings
● Sicherheitsüberlegungen
● http://unicodesnowmanforyou.com/
● 'U0001f4a9'
Wozu Unicode?
● Zur Darstellung und Verarbeitung von Texten
● Deckt die meisten verwendeten Schriftzeichen ab
(dz. ca. 110.000 für ca. 100 Schriften)
● Weit verbreitet und unterstützt
(Python, XML, Java, .NET, etc)
● Erste publizierte Version: 1991
Unicode ist ein Mapping
● Computer verarbeiten nur Zahlen
● Für Anzeige: Umwandlung der Zahlen in
Buchstaben und andere Zeichen
Zeichen Dezimaler
Wert
Unicode
Code Point
A 65 U+0041
ü 252 U+00fc
€ 8364 U+20ac
樹 27193 U+6a39
Hex-Notation
Hex-Notation
U+20ac
● Präfix „U+“ für Unicode Code Point (in Hex)
● Zahlen auf Basis 16 statt 10
● Ziffern 0-9 wie Dezimal, Ziffern a-f = 10-15
Hex-Notation
U+20ac
euro_sign = '€'
euro_sign = 'u20ac'
euro_sign = chr(0x20ac) # Python 2: unichr(0x20a0)
euro_sign = chr(2 * 16**3 + 0 * 16**2 + 10 * 16**1 + 12 * 16**0)
euro_sign = chr(8364)
euro_sign = 'N{EURO SIGN}'
import unicodedata
euro_sign = unicodedata.lookup('EURO SIGN')
Unicode Zeichenketten
in Python 3
Unicode Zeichenketten
english_text = 'cheese spaetzle: 10 euro'
german_text = 'Käsespätzle: 10€'
german_text = u'Käsespätzle: 10€' # Python 3.3+
german_text = 'Ku00e4sespu00e4tzle: 10u20ac'
Encodings
Encodings
● Externe Darstellung von Zeichenketten
● Für Datenaustausch über Dateien,
Netzwerkverbindungen etc
● Viele gebräuchliche Encodings älter als Unicode
● Können teilweise nur Teile von Unicode darstellen
Encodings
● Interne Darstellung von Unicode-Zeichen i.d.R. als
32 Bit Integer (4 Byte)
● Encodings stellen Zeichenketten i.d.R. als Folge von
8 Bit Integers (1 Byte) dar
print('u20ac'.encode('utf-8')) # euro sign as UTF-8
b'xe2x82xac'
UTF-32 (UCS-4, ISO 10646)
● Ordnet jedem Unicode-Zeichen einen 32 Bit Integer
zu
● Per Definition zwischen 0 und 0x7fffffff (31 Bit)
● In der Praxis zwischen 0 und 0x0010ffff (21 bit)
● Jedes Zeichen benötigt genau 4 Byte
● Technisch „logischste“ Art der Darstellung
● Interne Darstellung vieler Programmiersprachen
UTF-32 (UCS-4, ISO 10646)
# This code works in Python 2.6+ and 3.3+.
import io
with io.open('menu.txt', 'w', encoding='utf-32') as menu_file:
menu_file.write(u'Spätzle: 10€')
$ hexdump -C menu.txt
00000000 ff fe 00 00 53 00 00 00 70 00 00 00 e4 00 00 00 |....S...p.......|
00000010 74 00 00 00 7a 00 00 00 6c 00 00 00 65 00 00 00 |t...z...l...e...|
00000020 3a 00 00 00 20 00 00 00 31 00 00 00 30 00 00 00 |:... ...1...0...|
00000030 ac 20 00 00 |. ..|
UTF-32 (UCS-4, ISO 10646)
# This code works in Python 2.6+ and 3.3+.
import io
with io.open('menu.txt', 'w', encoding='utf-32') as menu_file:
menu_file.write(u'Spätzle: 10€')
$ hexdump -C menu.txt
00000000 ff fe 00 00 53 00 00 00 70 00 00 00 e4 00 00 00 |....S...p.......|
00000010 74 00 00 00 7a 00 00 00 6c 00 00 00 65 00 00 00 |t...z...l...e...|
00000020 3a 00 00 00 20 00 00 00 31 00 00 00 30 00 00 00 |:... ...1...0...|
00000030 ac 20 00 00 |. ..|
UTF-32 (UCS-4, ISO 10646)
● Erfordert vergleichsweise viel
Speicherplatz
● Unterschiedliche Darstellung von 32
Bit Integer je nach CPU-Architektur
(„endianess“)
● Kann 0-Bytes enthalten
→ markiert Ende von Zeichenketten
in C
UTF-32 (UCS-4, ISO 10646)
# This code works in Python 2.6+ and 3.2+.
import io
with io.open('menu.txt', 'w', encoding='utf-32') as menu_file:
menu_file.write(u'Spätzle: 10€')
$ hexdump -C menu.txt
Byte Order Mark
● 0xfeff0000 = „big endian“ (ARM, Motorola)
● 0x0000fffe = „little endian“ (Intel)
● Wenn kein BOM:
– Offiziell: „big endian“ annehmen
– In der Praxis: viele Anwendungen nehmen „little
endian“ an, da dies Windows unter Intel entspricht
– Work around: nach Leerzeichen suchen:
0x20000000=big endian, 0x00000020=little endian
Byte Order Mark
● Vorsicht bei string.encode(): BOM wird bei jedem
Aufruf angehängt
>>> 'a'.encode('utf-32') + 'b'.encode('utf-32')
b'xffxfex00x00ax00x00x00xffxfex00x00bx00x00x00'
● Abhilfe: Endianess explizit angeben ('le', 'be'):
>>> 'a'.encode('utf-32') + 'b'.encode('utf-32le')
b'xffxfex00x00ax00x00x00bx00x00x00'
● Kein Problem bei io.open():nur erstes write() schreibt
BOM
UTF-16
● Teilt die 4 Byte aus UTF-32 auf in 2 mal 2 Byte
● Nur dann 4 Byte, wenn Code > 0xffff
● „Magic“ zwischen 0xd800 und 0xdfff
UTF-16
00000000 ff fe 53 00 70 00 e4 00 74 00 7a 00 6c 00 65 00 |..S.p...t.z.l.e.|
00000010 3a 00 20 00 31 00 30 00 ac 20 |:. .1.0.. |
● Hat BOM
● Kann 0-Bytes enthalten
● Vorteil: für westliche Encodings 50% weniger
Platzbedarf als UTF-32
● Länge einer Zeichenkette nur mit Dekodieren
ermittelbar
UCS-2
● Ähnlich UTF-16 aber nur für 0 bis 0xffff (16 Bit)
● „Jugendsünde“ von Unicode, als Chinesisch noch
als irrelevant betrachtet wurde
● Nicht verwenden um Daten
zu schreiben oder senden
● Weit verbreitet bei
Anwendungen, die früh
begannen, Unicode zu
unterstützen.
UTF-8
● Wandelt Zeichen um in 1 bis 4 Bytes
● Codes 0 bis 0x7f entsprechen den ASCII-Zeichen
● Codes 0x80 bis 0xff bedeuten, dass mehrere Bytes
zu verbinden sind, um das eigentliche Zeichen zu
erhalten
● „Zusammenbauen“ des eigentlichen Zeichencodes
über Bit-Operationen
https://en.wikipedia.org/wiki/Utf-8
UTF-8
● Kann 0-Bytes enthalten: 0 bleibt 0.
● Variante: Modified UTF-8 (MUTF-8): wandelt 0 um
in 0xc0 0x80 → für C verwendbar
● Speicherbedarf:
– Für englische Texte: 75% geringer als UTF-32
– Für deutsche Texte: ca. 70% geringer als UTF-32
UTF-8
● Kein BOM erforderlich, da sowohl auf big als auch
little endian das Ergebnis gleich ist
● Aber: BOM 0xefbbbf wird in der Praxis häufig
verwendet, um UTF-8 als solchen erkennbar zu
machen
● Microsoft Anwendungen ergänzen (Notepad) oder
erwarten (Compiler) BOM für UTF-8 Texte
UTF-8
● Sehr populär, verwendet für
– Python 3 Quellcodes (sofern nicht anders angegeben)
– XML (sofern nicht anders angegeben)
– Java String Serialization (MUTF-8)
● Verarbeitbar für Software, die eigentlich keinen
Unicode unterstützt (zB Lua-Strings)
● Windows Codepage: 65001
● Für ASCII-Texte ident
UTF-8
Diskriminierend: Amerikaner
benötigen weniger Speicher
als zB Chinesen
Zusammenfassung UTF-Encodings
Encoding
Ermittlung der
Länge
Speicherbedarf
Deutsch
Speicherbedarf
Chinesisch
UTF-32 O(1) 4 * n 4 * n
UTF-16 O(n) 2 * n 4 * n
UCS-2 O(1) 2 * n n/a
UTF-8 O(n) ca. 1.1 * n 4 * n
8 Bit Encodings
● Aus der Zeit vor Unicode
● Effizient:
– Immer 1 Byte pro Zeichen
– Ermittlung der Länge O(1)
8 Bit Encodings: Herausforderungen
● Umlaute, scharfes S: ä ö ü ß Ä Ö Ü
● Euro: €
ASCII
● „American Standard Code for
Information Interchange“
● Entstanden 1969
● Deckt die Anforderungen der
englischsprachigen Länder zur
Zeit der Mondlandung ab
https://de.wikipedia.org/wiki/Datei:Apollo11-Aldrin-Ausstieg.jpg
ASCII
● Buchstaben A-Z, Ziffern 0-9, Satzzeichen
● Einige Sonderzeichen wie , {, }, $ etc
● Steuerzeichen (0x00 bis 0x1f)
● Erfordert nur 7 Bit (Codes 0 bis 0x7f)
ASCII
● Keine Umlaute
● Kein Euro
● Für praktisch alle Anwendungsfälle unbrauchbar
● ASCII-Texte „immer verarbeitbar“
● Oft der Default wenn nicht anders angegeben
Latin-1 / ISO-8859-1
● Entstanden 1987
● Deckt die Anforderung vieler
westeuropäischen Länder ab als
„Never gonna give you up“
Nummer 1 war
● Default Encoding für:
– HTML
– Python Quellcodes bis 2.4
https://de.wikipedia.org/wiki/Datei:Rick_Astley-cropped.jpg
Latin-1 / ISO-8859-1
Latin-1 / ISO-8859-1
● 0x00 bis 0x7ff ident mit ASCII
● 0x80 bis 0x9f: unbenutzt - 7 Bit Terminals würden
diese Zeichen in 0x00 bis 0x1f umwandeln →
Steuerzeichen→ Chaos
● 0xa0 bis 0xff: diverse regionale Sonderzeichen
● Umlaute
● Kein Euro
Latin-9 / ISO-8859-15
● Entstanden 1999, gleichzeitig mit Counter
Strike und der Euro-Einführung
● Weitgehend ident mit Latin-1
● Einige Zeichen umdefiniert, insbesondere:
Euro = 0xa4
https://en.wikipedia.org/wiki/File:Counter-Strike_screenshot.png
Latin-9 / ISO-8859-15
Latin-9 / ISO-8859-15
● 0x08 bis 0x9f weiterhin unbenutzt (wegen 7 Bit
Terminals)
● „nicht ganz“ kompatibel mit Latin-1
CP1252 / Windows-1252
● Alle Zeichen von Latin-1 mit selbem
Code
● Alle zusätzlichen Zeichen von Latin-9
mit anderem Code
● Einige weitere Zeichen, insbesondere
„smart quotes“
● Oft als „Windows ANSI“ bezeichnet (ist
aber von Microsoft und nicht vom
American National Standard Institute)
https://de.wikipedia.org/wiki/Datei:Vorfenster_additional_window_fenetre_pour_l_hiver.JPG
CP1252 / Windows-1252
CP1252 / Windows-1252
● Umlaute
● Euro = 0x80
● Kann bei Anzeige auf 7 Bit Terminals zu
Steuerzeichen führen
● Best practice: Wenn Dokument selbst sagt, dass es
Latin-1 ist, dann CP1252 annehmen
(zB W3C Recommendation in HTML5)
Zusammenfassung 8 Bit Encodings
Encoding Umlaute? Euro?
ASCII Nein Nein
Latin-1 Ja Nein
Latin-9 Ja Ja, bei 0xa4
CP1252 Ja Ja, bei 0x80
Empfehlungen für deutsche Texte
● Vorzugsweise UTF-8 verwenden
● Wenn 8 Bit Encoding erforderlich, vorzugsweise
CP1252; falls nicht vorhanden, dann Latin-1
● 8 Bit Encodings nur verwenden wenn:
– Anwendung / Protokoll / System Unicode noch nicht
unterstützt
– Migration unwirtschaftlich ist
– Speichereffizient kritisch ist
EBCDIC
EBCDIC
● „Extended Binary Coded Decimal Interchange
Code“
● Entstanden um 1963
● Verwendet auf Großrechner
● Nicht verwenden außer externe Daten erfordern es.
CP500
● Zeichen für englische Texte (und einige
Sonderzeichen)
● Bedingt Vergleichbar mit ASCII
● Encoding für Dateisystem. Gültige Zeichen in
Namen: a-z, 0-9, $, @, TODO; Trennzeichen „.“, „(“
und „)“.
CP500
CP273
● Für westeuropäische Länder
● Vergleichbar mit ISO-8859-1 / Latin-1
CP1141
● Ähnlich CP273 aber mit Euro-Zeichen
● Bedingt vergleichbar mit ISO-8859-15 und CP1252
Empfehlungen für EBCDIC
● Wenn möglich: EBCDIC nicht verwenden
● Für Quelldaten: CP1141
● Für Zieldaten: CP1252
● Für Dateinamen: CP500 (Systemvorgabe)
Wann Daten
en-/decoden?
Wann en- und decoden
● Beim Einlesen Text decoden
● Programmintern immer alle Texte als Unicode
● Bei Ausgabe Text encoden
Python-
Programm
Unicode
Eingabe-
Bytes
Ausgabe-
Bytes
Decode Encode
Ein- und Ausgabe für Text
● io.open(path, mode, encoding=...)
# mode='r' oder 'w'
● csv.reader(file, ...) # Nutzt Encoding von file
csv.writer(file, ...)
● xml.etree.ElementTree.parse(path)
xml.etree.ElementTree.write(
path, encoding=..., xml_declaration=True)
● io.StringIO # ließt / schreibt Unicode String
Ein- und Ausgabe für Byte-Daten
● io.open(path, mode) # mode='rb' oder 'wb'
● io.BytesIO # ließt/schreibt Bytes
● Kompatibilität: io-Modul ist verfügbar seit Python
2.6+
Python Default Encoding
Python Default Encoding
● Unterschiedliches Ergebnis je nach Python Version,
Betriebssystem, Mondphase und Tagesverfassung
● Empfehlung: ignorieren und immer explizites
Encoding nutzen
$ python2.6 -c "import sys; print(sys.getdefaultencoding())"
ascii
$ python3.4 -c "import sys; print(sys.getdefaultencoding())"
utf-8
Encoding für Terminal / Console
● Ähnlich hoffnungslos wie sys.getdefaultencoding()
● Unter Unix / Mac OS X:
$ export LC_ALL=en_US.UTF-8
● Unter Windows und cmd.exe:
chcp und UTF-8 Hacks wie beschrieben in
http://stackoverflow.com/questions/878972/
● Print() i.d.R. ok aber nicht notwendigerweise logging-Modul auf
sys.stderr, Ein-/Ausgabe mit sys.std*, Pipes, ...
● Unter Python 2 deterministisch praktisch immer kaputt,
unter Python 3 je nach Version.
Vorhandenes Encoding
ermitteln
Eingabe-Encoding
1.Dokumentation – oft einen Versuch wert
2.Beispieldaten analysieren
3.Heuristiken nutzen, zB
– Unicode, Dammit:
http://www.crummy.com/software/BeautifulSoup/bs4/do
c/#unicode-dammit
– Chardet: https://pypi.python.org/pypi/chardet
Beispieldaten analysieren
● Grundstrategie für deutsche Texte
● Auf BOM prüfen: wenn vorhanden, dann je nach
Ausprägung UTF-32, UTF-16 oder UTF-8
● Als Hexdump anzeigen und auf Umlaut-ü und Euro-
Zeichen prüfen
Beispieldaten analysieren
Zeichen Bytes Encoding
ü 0xc3 0xbc UTF-8
ü 0xfc CP1252, Latin-1 oder
Latin-9
€ 0xe2 0x82 0xac UTF-8
€ 0x80 CP1252
€ 0xa4 Latin-9
Beispiel 1
00000000: 46 61 6c 73 63 68 65 73 20 c3 9c 62 65 6e 20 76 |Falsches ..ben v|
0000000f: 6f 6e 20 58 79 6c 6f 70 68 6f 6e 6d 75 73 69 6b |on Xylophonmusik|
0000001f: 20 71 75 c3 a4 6c 74 20 6a 65 64 65 6e 20 67 72 | qu..lt jeden gr|
0000002f: c3 b6 c3 9f 65 72 65 6e 20 5a 77 65 72 67 20 66 |....eren Zwerg f|
0000003f: c3 bc 72 20 e2 82 ac 20 31 30 |..r ... 10|
Beispiel-Pangram
„Falsches Üben von Xylophonmusik quält jeden
größeren Zwerg für € 10“
Beispiel 1
00000000: 46 61 6c 73 63 68 65 73 20 c3 9c 62 65 6e 20 76 |Falsches ..ben v|
0000000f: 6f 6e 20 58 79 6c 6f 70 68 6f 6e 6d 75 73 69 6b |on Xylophonmusik|
0000001f: 20 71 75 c3 a4 6c 74 20 6a 65 64 65 6e 20 67 72 | qu..lt jeden gr|
0000002f: c3 b6 c3 9f 65 72 65 6e 20 5a 77 65 72 67 20 66 |....eren Zwerg f|
0000003f: c3 bc 72 20 e2 82 ac 20 31 30 |..r ... 10|
Beispiel 2
00000000: 46 61 6c 73 63 68 65 73 20 dc 62 65 6e 20 76 6f |Falsches .ben vo|
0000000f: 6e 20 58 79 6c 6f 70 68 6f 6e 6d 75 73 69 6b 20 |n Xylophonmusik |
0000001f: 71 75 e4 6c 74 20 6a 65 64 65 6e 20 67 72 f6 df |qu.lt jeden gr..|
0000002f: 65 72 65 6e 20 5a 77 65 72 67 20 66 fc 72 20 80 |eren Zwerg f.r .|
0000003f: 20 31 30 | 10|
Beispiel 2
00000000: 46 61 6c 73 63 68 65 73 20 dc 62 65 6e 20 76 6f |Falsches .ben vo|
0000000f: 6e 20 58 79 6c 6f 70 68 6f 6e 6d 75 73 69 6b 20 |n Xylophonmusik |
0000001f: 71 75 e4 6c 74 20 6a 65 64 65 6e 20 67 72 f6 df |qu.lt jeden gr..|
0000002f: 65 72 65 6e 20 5a 77 65 72 67 20 66 fc 72 20 80 |eren Zwerg f.r .|
0000003f: 20 31 30 | 10|
UnicodeError
UnicodeEncodeError
>>> 'Spätzle: 10€'.encode('ascii')
Traceback (most recent call last):
File "examples.py", line 78, in <module>
'Spätzle: 10€'.encode('ascii')
UnicodeEncodeError: 'ascii' codec can't encode character 'xe4' in
position 2: ordinal not in range(128)
UnicodeDecodeError
>>> spaetzle_bytes = 'Spätzle: 10€'.encode('cp1252')
>>> print(spaetzle_bytes)
b'Spxe4tzle: 10x80'
>>> spaetzle_bytes.decode('utf-8')
Traceback (most recent call last):
File "/Users/agi/workspace/talks/pygraz/unicode/examples.py",
line 85, in <module>
spaetzle_bytes.decode('utf-8')
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe4 in
position 2: invalid continuation byte
Was tun bei UnicodeError?
Was tun bei UnicodeError?
● Jammern
● Weinen
● Brüllen
● Dinge durch den
Raum werfen
● Schoklade essen
● Etc etc etc
UnicodeError vermeiden
● Empfehlungen von vorhin folgen
● Früh decoden
● Intern nur Unicode
● Spät encoden
● Explizites Encoding
angeben
J. Robinson, Bomb Queen WMD – Woman of mass destruction, p. 7
Was tun wenn doch UnicodeError?
● Daten vor (De-)Kodieren im Rohformat ausgeben:
print('%r' % data)
● errors='replace' setzen und das Ergebnis analysieren
● encodings.cp1252 „verbessern“, so dass jedes Byte
(de-)kodierbar ist und das Ergebnis analysieren.
● Alles Workarounds um Fehler zu
finden → nach Lösungsfindung
wieder abbauen und sauber
umsetzen
CP1252 „verbessern“
● Nicht alle Bytes sind mapbar
● Einige mappen auf „undefined“ → UnicodeError
decoding_table = (
'x00' # 0x00 -> NULL
'x01' # 0x01 -> START OF HEADING
'x02' # 0x02 -> START OF TEXT
...
'}' # 0x7D -> RIGHT CURLY BRACKET
'~' # 0x7E -> TILDE
'x7f' # 0x7F -> DELETE
'u20ac' # 0x80 -> EURO SIGN
'ufffe' # 0x81 -> UNDEFINED
'u201a' # 0x82 -> SINGLE LOW-9 QUOTATION MARK
'u0192' # 0x83 -> LATIN SMALL LETTER F WITH HOOK
'u201e' # 0x84 -> DOUBLE LOW-9 QUOTATION MARK
…
)
CP1255 „verbessern“
import codecs
from encodings import cp1252
decoding_table = ''.join([
code if code != 'ufffe' else chr(index) 
for index, code in enumerate(cp1252.decoding_table)
])
assert 'ufffe' not in decoding_table
cp1252.decoding_table = decoding_table
cp1252.encoding_table = codecs.charmap_build(decoding_table)
# Führt normalerweise zu UnicodeEncodeError.
print('x81'.encode('cp1252'))
Unicode Normalisierung
Relevanz von Normalisierung
● In vielen Fällen keine Relevanz, insbesondere:
– Daten kommen alle aus dem selben System (zB eine
zentrale Datenbank)
– Daten werden nur angezeigt oder ausgewertet
● Vor allem dann ein Thema, wenn:
– Datenaustausch mit externen Systemen und
unterschiedlichen Plattformen
– Daten werden verglichen
Gleichaussehende Zeichen
Verschiedene Zeichen, die gleich aussehen:
● Großes I: U+0049
● Römische 1: U+2160
>>> print('u0049')
I
>>> print('u2160')
Ⅰ
Verschiedene Darstellungen
Verschiedene Darstellungen des selben Zeichen, zB Umlaut-ä:
● Ein Zeichen: U+00e4
● zusammengesetztes Zeichen: U+0061 U+0308
('a' + zwei Punkte darüber)
print_hexdump([code for code in 
unicodedata.normalize('NFC', 'ä').encode('utf-16be')])
00000000: 00 e4 |..|
print_hexdump([code for code in 
unicodedata.normalize('NFD', 'ä').encode('utf-16be')])
00000000: 00 61 03 08 |.a..|
Normalisierung in Filesystem
Keine Einheitlichkeit: Beispiel:
with io.open('u0049.tmp', 'wb'):
pass
with io.open('u2160.tmp', 'wb'):
pass
Ergebnis mit Mac OS Extended Filesystem:
$ ls -1 *.tmp
I.tmp
Ⅰ.tmp
Vergleichen mit Normalisierung
● NFC - wenn inhaltlich gleiche Zeichen gleich sein sollen
● NFKC - wenn auch optisch gleiche Zeichen gleich sein sollen
(Sicherheitsthema)
● NFD, NFKD – gleiches Ergebnis, aber erfordert unter Umständen
mehr Speicher
from unicodedata import normalize
print(normalize('NFC', 'u00e4') == normalize('NFC', 'u0061u0308'))
# True – umlaut ä
print(normalize('NFC', 'u0049') == normalize('NFC', 'u2160'))
# False – letter I and Roman number I
print(normalize('NFKC', 'u00e4') == normalize('NFKC', 'u0061u0308'))
# True – umlaut ä
print(normalize('NFKC', 'u0049') == normalize('NFKC', 'u2160'))
# True – letter I and Roman number I
Empfehlungen zu Normalisierung
● Erst dann zum Problem machen, wenn es eines ist.
● Wenn möglich durchgängig einheitlich
normalisieren (zB NFKC)
● Sonst je Eingangs-/Ausgangsystem beim
Lesen/Schreiben die erforderliche Normalisierung
nutzen
● Bei Dateinamen: abhängig vom Filesystem, für
Python nicht feststellbar (zB NFS).
Unicode Zeichenketten
in Python 2
Datentypen Python 2 und 3
Code Python 2.6+ Python 3.2+
'x' str (8 Bit) str (Unicode)
b'x' str (8 Bit) bytes (8 Bit)
u'x' unicode (Unicode) str (Unicode)
Python 2 wie Python 3
● from __future__ import unicode_literals
– '…' entspricht u'...'
● io.open() statt open()
● io.StringIO statt StringIO und cStringIO
● Wo Byte-Strings erforderlich explizit b'...' verwenden
Beispiel: csv.reader(file, delimter=b';')
● Aber: manche Unterschiede bleiben bestehen, zB:
– str() - unicode()
– __str__ - __unicode__
– chr() – unichr()
– Schmerzlinderung: six.*_types etc → https://pypi.python.org/pypi/six
Unicode in Python 2 und 3
● Ab 2.0: grundsätzlich unterstützt (u'text', codecs und
unicodedata Modul)
● Ab 2.5: __future__.unicode_literals Modul
● Ab 2.6: io Modul
● Ab 3.0: durchgängig aber teilweise inkompatibel zu
Python 2 unterstützt (kein u'text')
● Ab 3.3: wieder mit u'text'; verbesserte Erkennung
für Terminal-Encoding
Empfehlung
● Wenn möglich Python 3.3+ verwenden
● Sonst: wenn möglich Python 2.6+ verwenden
● Sonst: andere Programmiersprache mit guter
Unicode-Implementierung verwenden
(zB Java, C#, go, etc)
Zusammenfassung
Zusammenfassung
● Python unterstützt Unicode je nach Version in
unterschiedlicher Qualität
● früh decoden, intern alles Unicode, spät encoden
● Encodings
– fix setzen, nicht auf Automatismen hoffen
– ggf. selbst herausfinden mit Hexdump, ü und €
– besonders nützlich: UTF-8, CP1252
● Python 2 Module für Unicode ähnlich Python 3:
io, unicode_literals

Contenu connexe

Similaire à Erste-Hilfekasten für Unicode mit Python

C API for Lotus Notes & Domino
C API for Lotus Notes & DominoC API for Lotus Notes & Domino
C API for Lotus Notes & DominoUlrich Krause
 
C / C++ Api for Beginners
C / C++ Api for BeginnersC / C++ Api for Beginners
C / C++ Api for BeginnersUlrich Krause
 
Übungsaufgaben SS2010
Übungsaufgaben SS2010Übungsaufgaben SS2010
Übungsaufgaben SS2010maikinger
 
Programmieren mit PROLOG
Programmieren mit PROLOGProgrammieren mit PROLOG
Programmieren mit PROLOGQAware GmbH
 
CadSoft - Elektro Anwendungen - Tutorial Version 4
CadSoft - Elektro Anwendungen - Tutorial Version 4CadSoft - Elektro Anwendungen - Tutorial Version 4
CadSoft - Elektro Anwendungen - Tutorial Version 4Juliane Tran Cong
 
esp8266 Demo - bonn agile tech talks
esp8266 Demo - bonn agile tech talksesp8266 Demo - bonn agile tech talks
esp8266 Demo - bonn agile tech talksAndreas Kluth
 
C/ C++ for Notes & Domino Developers
C/ C++ for Notes & Domino DevelopersC/ C++ for Notes & Domino Developers
C/ C++ for Notes & Domino DevelopersUlrich Krause
 
Einfürung iPhone Entwicklung
Einfürung iPhone EntwicklungEinfürung iPhone Entwicklung
Einfürung iPhone EntwicklungStuff Mc
 
Drahtwanderung: Wir machen den NeXTen Schritt
Drahtwanderung: Wir machen den NeXTen SchrittDrahtwanderung: Wir machen den NeXTen Schritt
Drahtwanderung: Wir machen den NeXTen SchrittFalk Hartmann
 
Status of syslog as of 2005
Status of syslog as of 2005Status of syslog as of 2005
Status of syslog as of 2005Rainer Gerhards
 
C Sharp Einfuehrung Teil 1
C Sharp Einfuehrung Teil 1C Sharp Einfuehrung Teil 1
C Sharp Einfuehrung Teil 1DraphonyGames
 

Similaire à Erste-Hilfekasten für Unicode mit Python (14)

C API for Lotus Notes & Domino
C API for Lotus Notes & DominoC API for Lotus Notes & Domino
C API for Lotus Notes & Domino
 
Schei. encoding
Schei. encodingSchei. encoding
Schei. encoding
 
C / C++ Api for Beginners
C / C++ Api for BeginnersC / C++ Api for Beginners
C / C++ Api for Beginners
 
Übungsaufgaben SS2010
Übungsaufgaben SS2010Übungsaufgaben SS2010
Übungsaufgaben SS2010
 
Programmieren mit PROLOG
Programmieren mit PROLOGProgrammieren mit PROLOG
Programmieren mit PROLOG
 
CadSoft - Elektro Anwendungen - Tutorial Version 4
CadSoft - Elektro Anwendungen - Tutorial Version 4CadSoft - Elektro Anwendungen - Tutorial Version 4
CadSoft - Elektro Anwendungen - Tutorial Version 4
 
esp8266 Demo - bonn agile tech talks
esp8266 Demo - bonn agile tech talksesp8266 Demo - bonn agile tech talks
esp8266 Demo - bonn agile tech talks
 
NRWConf2013_T4CodeGeneration
NRWConf2013_T4CodeGenerationNRWConf2013_T4CodeGeneration
NRWConf2013_T4CodeGeneration
 
C/ C++ for Notes & Domino Developers
C/ C++ for Notes & Domino DevelopersC/ C++ for Notes & Domino Developers
C/ C++ for Notes & Domino Developers
 
Einfürung iPhone Entwicklung
Einfürung iPhone EntwicklungEinfürung iPhone Entwicklung
Einfürung iPhone Entwicklung
 
Drahtwanderung: Wir machen den NeXTen Schritt
Drahtwanderung: Wir machen den NeXTen SchrittDrahtwanderung: Wir machen den NeXTen Schritt
Drahtwanderung: Wir machen den NeXTen Schritt
 
Status of syslog as of 2005
Status of syslog as of 2005Status of syslog as of 2005
Status of syslog as of 2005
 
Feature satip4
Feature satip4Feature satip4
Feature satip4
 
C Sharp Einfuehrung Teil 1
C Sharp Einfuehrung Teil 1C Sharp Einfuehrung Teil 1
C Sharp Einfuehrung Teil 1
 

Plus de roskakori

Expanding skill sets - Broaden your perspective on design
Expanding skill sets - Broaden your perspective on designExpanding skill sets - Broaden your perspective on design
Expanding skill sets - Broaden your perspective on designroskakori
 
Django trifft Flutter
Django trifft FlutterDjango trifft Flutter
Django trifft Flutterroskakori
 
Multiple django applications on a single server with nginx
Multiple django applications on a single server with nginxMultiple django applications on a single server with nginx
Multiple django applications on a single server with nginxroskakori
 
Helpful pre commit hooks for Python and Django
Helpful pre commit hooks for Python and DjangoHelpful pre commit hooks for Python and Django
Helpful pre commit hooks for Python and Djangoroskakori
 
Startmeeting Interessengruppe NLP NLU Graz
Startmeeting Interessengruppe NLP NLU GrazStartmeeting Interessengruppe NLP NLU Graz
Startmeeting Interessengruppe NLP NLU Grazroskakori
 
Helpful logging with python
Helpful logging with pythonHelpful logging with python
Helpful logging with pythonroskakori
 
Helpful logging with Java
Helpful logging with JavaHelpful logging with Java
Helpful logging with Javaroskakori
 
Einführung in Kommunikation und Konfliktmanagement für Software-Entwickler
Einführung in Kommunikation und Konfliktmanagement für Software-EntwicklerEinführung in Kommunikation und Konfliktmanagement für Software-Entwickler
Einführung in Kommunikation und Konfliktmanagement für Software-Entwicklerroskakori
 
Analyzing natural language feedback using python
Analyzing natural language feedback using pythonAnalyzing natural language feedback using python
Analyzing natural language feedback using pythonroskakori
 
Microsoft SQL Server with Linux and Docker
Microsoft SQL Server with Linux and DockerMicrosoft SQL Server with Linux and Docker
Microsoft SQL Server with Linux and Dockerroskakori
 
Migration to Python 3 in Finance
Migration to Python 3 in FinanceMigration to Python 3 in Finance
Migration to Python 3 in Financeroskakori
 
Introduction to pygments
Introduction to pygmentsIntroduction to pygments
Introduction to pygmentsroskakori
 
Lösungsorientierte Fehlerbehandlung
Lösungsorientierte FehlerbehandlungLösungsorientierte Fehlerbehandlung
Lösungsorientierte Fehlerbehandlungroskakori
 
XML namespaces and XPath with Python
XML namespaces and XPath with PythonXML namespaces and XPath with Python
XML namespaces and XPath with Pythonroskakori
 
Introduction to trader bots with Python
Introduction to trader bots with PythonIntroduction to trader bots with Python
Introduction to trader bots with Pythonroskakori
 
Open source projects with python
Open source projects with pythonOpen source projects with python
Open source projects with pythonroskakori
 
Python builds mit ant
Python builds mit antPython builds mit ant
Python builds mit antroskakori
 
Kanban zur Abwicklung von Reporting-Anforderungen
Kanban zur Abwicklung von Reporting-AnforderungenKanban zur Abwicklung von Reporting-Anforderungen
Kanban zur Abwicklung von Reporting-Anforderungenroskakori
 

Plus de roskakori (18)

Expanding skill sets - Broaden your perspective on design
Expanding skill sets - Broaden your perspective on designExpanding skill sets - Broaden your perspective on design
Expanding skill sets - Broaden your perspective on design
 
Django trifft Flutter
Django trifft FlutterDjango trifft Flutter
Django trifft Flutter
 
Multiple django applications on a single server with nginx
Multiple django applications on a single server with nginxMultiple django applications on a single server with nginx
Multiple django applications on a single server with nginx
 
Helpful pre commit hooks for Python and Django
Helpful pre commit hooks for Python and DjangoHelpful pre commit hooks for Python and Django
Helpful pre commit hooks for Python and Django
 
Startmeeting Interessengruppe NLP NLU Graz
Startmeeting Interessengruppe NLP NLU GrazStartmeeting Interessengruppe NLP NLU Graz
Startmeeting Interessengruppe NLP NLU Graz
 
Helpful logging with python
Helpful logging with pythonHelpful logging with python
Helpful logging with python
 
Helpful logging with Java
Helpful logging with JavaHelpful logging with Java
Helpful logging with Java
 
Einführung in Kommunikation und Konfliktmanagement für Software-Entwickler
Einführung in Kommunikation und Konfliktmanagement für Software-EntwicklerEinführung in Kommunikation und Konfliktmanagement für Software-Entwickler
Einführung in Kommunikation und Konfliktmanagement für Software-Entwickler
 
Analyzing natural language feedback using python
Analyzing natural language feedback using pythonAnalyzing natural language feedback using python
Analyzing natural language feedback using python
 
Microsoft SQL Server with Linux and Docker
Microsoft SQL Server with Linux and DockerMicrosoft SQL Server with Linux and Docker
Microsoft SQL Server with Linux and Docker
 
Migration to Python 3 in Finance
Migration to Python 3 in FinanceMigration to Python 3 in Finance
Migration to Python 3 in Finance
 
Introduction to pygments
Introduction to pygmentsIntroduction to pygments
Introduction to pygments
 
Lösungsorientierte Fehlerbehandlung
Lösungsorientierte FehlerbehandlungLösungsorientierte Fehlerbehandlung
Lösungsorientierte Fehlerbehandlung
 
XML namespaces and XPath with Python
XML namespaces and XPath with PythonXML namespaces and XPath with Python
XML namespaces and XPath with Python
 
Introduction to trader bots with Python
Introduction to trader bots with PythonIntroduction to trader bots with Python
Introduction to trader bots with Python
 
Open source projects with python
Open source projects with pythonOpen source projects with python
Open source projects with python
 
Python builds mit ant
Python builds mit antPython builds mit ant
Python builds mit ant
 
Kanban zur Abwicklung von Reporting-Anforderungen
Kanban zur Abwicklung von Reporting-AnforderungenKanban zur Abwicklung von Reporting-Anforderungen
Kanban zur Abwicklung von Reporting-Anforderungen
 

Erste-Hilfekasten für Unicode mit Python

  • 1. Erste Hilfekasten für Unicode mit Python Version 1.2 Thomas Aglassinger https://github.com/roskakori/talks/tree/master/pygraz/unicode
  • 2. Agenda 1.Wozu Unicode? 2.Unicode Zeichenketten in Python 3 3.Encodings 4.Wann Daten (de-)kodieren? 5.Vorhandenes Encoding ermitteln 6.UnicodeError 7.Unicode Zeichenketten in Python 2
  • 3. Ziele Die Teilnehmer können... ● ...Texte mit Umlauten in Python verarbeiten. ● ...ein geeignetes Encoding wählen. ● ...die Stärken und Schwächen der wichtigsten Encodings zuordnen. ● ...UnicodeError vermeiden.
  • 4. Nicht-Themen ● Verstehen der Encodings auf Bit-Ebene → Wikipedia ● Implementieren eigener Encodings ● Sicherheitsüberlegungen ● http://unicodesnowmanforyou.com/ ● 'U0001f4a9'
  • 5. Wozu Unicode? ● Zur Darstellung und Verarbeitung von Texten ● Deckt die meisten verwendeten Schriftzeichen ab (dz. ca. 110.000 für ca. 100 Schriften) ● Weit verbreitet und unterstützt (Python, XML, Java, .NET, etc) ● Erste publizierte Version: 1991
  • 6. Unicode ist ein Mapping ● Computer verarbeiten nur Zahlen ● Für Anzeige: Umwandlung der Zahlen in Buchstaben und andere Zeichen Zeichen Dezimaler Wert Unicode Code Point A 65 U+0041 ü 252 U+00fc € 8364 U+20ac 樹 27193 U+6a39
  • 8. Hex-Notation U+20ac ● Präfix „U+“ für Unicode Code Point (in Hex) ● Zahlen auf Basis 16 statt 10 ● Ziffern 0-9 wie Dezimal, Ziffern a-f = 10-15
  • 9. Hex-Notation U+20ac euro_sign = '€' euro_sign = 'u20ac' euro_sign = chr(0x20ac) # Python 2: unichr(0x20a0) euro_sign = chr(2 * 16**3 + 0 * 16**2 + 10 * 16**1 + 12 * 16**0) euro_sign = chr(8364) euro_sign = 'N{EURO SIGN}' import unicodedata euro_sign = unicodedata.lookup('EURO SIGN')
  • 11. Unicode Zeichenketten english_text = 'cheese spaetzle: 10 euro' german_text = 'Käsespätzle: 10€' german_text = u'Käsespätzle: 10€' # Python 3.3+ german_text = 'Ku00e4sespu00e4tzle: 10u20ac'
  • 13. Encodings ● Externe Darstellung von Zeichenketten ● Für Datenaustausch über Dateien, Netzwerkverbindungen etc ● Viele gebräuchliche Encodings älter als Unicode ● Können teilweise nur Teile von Unicode darstellen
  • 14. Encodings ● Interne Darstellung von Unicode-Zeichen i.d.R. als 32 Bit Integer (4 Byte) ● Encodings stellen Zeichenketten i.d.R. als Folge von 8 Bit Integers (1 Byte) dar print('u20ac'.encode('utf-8')) # euro sign as UTF-8 b'xe2x82xac'
  • 15. UTF-32 (UCS-4, ISO 10646) ● Ordnet jedem Unicode-Zeichen einen 32 Bit Integer zu ● Per Definition zwischen 0 und 0x7fffffff (31 Bit) ● In der Praxis zwischen 0 und 0x0010ffff (21 bit) ● Jedes Zeichen benötigt genau 4 Byte ● Technisch „logischste“ Art der Darstellung ● Interne Darstellung vieler Programmiersprachen
  • 16. UTF-32 (UCS-4, ISO 10646) # This code works in Python 2.6+ and 3.3+. import io with io.open('menu.txt', 'w', encoding='utf-32') as menu_file: menu_file.write(u'Spätzle: 10€') $ hexdump -C menu.txt 00000000 ff fe 00 00 53 00 00 00 70 00 00 00 e4 00 00 00 |....S...p.......| 00000010 74 00 00 00 7a 00 00 00 6c 00 00 00 65 00 00 00 |t...z...l...e...| 00000020 3a 00 00 00 20 00 00 00 31 00 00 00 30 00 00 00 |:... ...1...0...| 00000030 ac 20 00 00 |. ..|
  • 17. UTF-32 (UCS-4, ISO 10646) # This code works in Python 2.6+ and 3.3+. import io with io.open('menu.txt', 'w', encoding='utf-32') as menu_file: menu_file.write(u'Spätzle: 10€') $ hexdump -C menu.txt 00000000 ff fe 00 00 53 00 00 00 70 00 00 00 e4 00 00 00 |....S...p.......| 00000010 74 00 00 00 7a 00 00 00 6c 00 00 00 65 00 00 00 |t...z...l...e...| 00000020 3a 00 00 00 20 00 00 00 31 00 00 00 30 00 00 00 |:... ...1...0...| 00000030 ac 20 00 00 |. ..|
  • 18. UTF-32 (UCS-4, ISO 10646) ● Erfordert vergleichsweise viel Speicherplatz ● Unterschiedliche Darstellung von 32 Bit Integer je nach CPU-Architektur („endianess“) ● Kann 0-Bytes enthalten → markiert Ende von Zeichenketten in C
  • 19. UTF-32 (UCS-4, ISO 10646) # This code works in Python 2.6+ and 3.2+. import io with io.open('menu.txt', 'w', encoding='utf-32') as menu_file: menu_file.write(u'Spätzle: 10€') $ hexdump -C menu.txt
  • 20. Byte Order Mark ● 0xfeff0000 = „big endian“ (ARM, Motorola) ● 0x0000fffe = „little endian“ (Intel) ● Wenn kein BOM: – Offiziell: „big endian“ annehmen – In der Praxis: viele Anwendungen nehmen „little endian“ an, da dies Windows unter Intel entspricht – Work around: nach Leerzeichen suchen: 0x20000000=big endian, 0x00000020=little endian
  • 21. Byte Order Mark ● Vorsicht bei string.encode(): BOM wird bei jedem Aufruf angehängt >>> 'a'.encode('utf-32') + 'b'.encode('utf-32') b'xffxfex00x00ax00x00x00xffxfex00x00bx00x00x00' ● Abhilfe: Endianess explizit angeben ('le', 'be'): >>> 'a'.encode('utf-32') + 'b'.encode('utf-32le') b'xffxfex00x00ax00x00x00bx00x00x00' ● Kein Problem bei io.open():nur erstes write() schreibt BOM
  • 22. UTF-16 ● Teilt die 4 Byte aus UTF-32 auf in 2 mal 2 Byte ● Nur dann 4 Byte, wenn Code > 0xffff ● „Magic“ zwischen 0xd800 und 0xdfff
  • 23. UTF-16 00000000 ff fe 53 00 70 00 e4 00 74 00 7a 00 6c 00 65 00 |..S.p...t.z.l.e.| 00000010 3a 00 20 00 31 00 30 00 ac 20 |:. .1.0.. | ● Hat BOM ● Kann 0-Bytes enthalten ● Vorteil: für westliche Encodings 50% weniger Platzbedarf als UTF-32 ● Länge einer Zeichenkette nur mit Dekodieren ermittelbar
  • 24. UCS-2 ● Ähnlich UTF-16 aber nur für 0 bis 0xffff (16 Bit) ● „Jugendsünde“ von Unicode, als Chinesisch noch als irrelevant betrachtet wurde ● Nicht verwenden um Daten zu schreiben oder senden ● Weit verbreitet bei Anwendungen, die früh begannen, Unicode zu unterstützen.
  • 25. UTF-8 ● Wandelt Zeichen um in 1 bis 4 Bytes ● Codes 0 bis 0x7f entsprechen den ASCII-Zeichen ● Codes 0x80 bis 0xff bedeuten, dass mehrere Bytes zu verbinden sind, um das eigentliche Zeichen zu erhalten ● „Zusammenbauen“ des eigentlichen Zeichencodes über Bit-Operationen https://en.wikipedia.org/wiki/Utf-8
  • 26. UTF-8 ● Kann 0-Bytes enthalten: 0 bleibt 0. ● Variante: Modified UTF-8 (MUTF-8): wandelt 0 um in 0xc0 0x80 → für C verwendbar ● Speicherbedarf: – Für englische Texte: 75% geringer als UTF-32 – Für deutsche Texte: ca. 70% geringer als UTF-32
  • 27. UTF-8 ● Kein BOM erforderlich, da sowohl auf big als auch little endian das Ergebnis gleich ist ● Aber: BOM 0xefbbbf wird in der Praxis häufig verwendet, um UTF-8 als solchen erkennbar zu machen ● Microsoft Anwendungen ergänzen (Notepad) oder erwarten (Compiler) BOM für UTF-8 Texte
  • 28. UTF-8 ● Sehr populär, verwendet für – Python 3 Quellcodes (sofern nicht anders angegeben) – XML (sofern nicht anders angegeben) – Java String Serialization (MUTF-8) ● Verarbeitbar für Software, die eigentlich keinen Unicode unterstützt (zB Lua-Strings) ● Windows Codepage: 65001 ● Für ASCII-Texte ident
  • 30. Zusammenfassung UTF-Encodings Encoding Ermittlung der Länge Speicherbedarf Deutsch Speicherbedarf Chinesisch UTF-32 O(1) 4 * n 4 * n UTF-16 O(n) 2 * n 4 * n UCS-2 O(1) 2 * n n/a UTF-8 O(n) ca. 1.1 * n 4 * n
  • 31. 8 Bit Encodings ● Aus der Zeit vor Unicode ● Effizient: – Immer 1 Byte pro Zeichen – Ermittlung der Länge O(1)
  • 32. 8 Bit Encodings: Herausforderungen ● Umlaute, scharfes S: ä ö ü ß Ä Ö Ü ● Euro: €
  • 33. ASCII ● „American Standard Code for Information Interchange“ ● Entstanden 1969 ● Deckt die Anforderungen der englischsprachigen Länder zur Zeit der Mondlandung ab https://de.wikipedia.org/wiki/Datei:Apollo11-Aldrin-Ausstieg.jpg
  • 34. ASCII ● Buchstaben A-Z, Ziffern 0-9, Satzzeichen ● Einige Sonderzeichen wie , {, }, $ etc ● Steuerzeichen (0x00 bis 0x1f) ● Erfordert nur 7 Bit (Codes 0 bis 0x7f)
  • 35. ASCII ● Keine Umlaute ● Kein Euro ● Für praktisch alle Anwendungsfälle unbrauchbar ● ASCII-Texte „immer verarbeitbar“ ● Oft der Default wenn nicht anders angegeben
  • 36. Latin-1 / ISO-8859-1 ● Entstanden 1987 ● Deckt die Anforderung vieler westeuropäischen Länder ab als „Never gonna give you up“ Nummer 1 war ● Default Encoding für: – HTML – Python Quellcodes bis 2.4 https://de.wikipedia.org/wiki/Datei:Rick_Astley-cropped.jpg
  • 38. Latin-1 / ISO-8859-1 ● 0x00 bis 0x7ff ident mit ASCII ● 0x80 bis 0x9f: unbenutzt - 7 Bit Terminals würden diese Zeichen in 0x00 bis 0x1f umwandeln → Steuerzeichen→ Chaos ● 0xa0 bis 0xff: diverse regionale Sonderzeichen ● Umlaute ● Kein Euro
  • 39. Latin-9 / ISO-8859-15 ● Entstanden 1999, gleichzeitig mit Counter Strike und der Euro-Einführung ● Weitgehend ident mit Latin-1 ● Einige Zeichen umdefiniert, insbesondere: Euro = 0xa4 https://en.wikipedia.org/wiki/File:Counter-Strike_screenshot.png
  • 41. Latin-9 / ISO-8859-15 ● 0x08 bis 0x9f weiterhin unbenutzt (wegen 7 Bit Terminals) ● „nicht ganz“ kompatibel mit Latin-1
  • 42. CP1252 / Windows-1252 ● Alle Zeichen von Latin-1 mit selbem Code ● Alle zusätzlichen Zeichen von Latin-9 mit anderem Code ● Einige weitere Zeichen, insbesondere „smart quotes“ ● Oft als „Windows ANSI“ bezeichnet (ist aber von Microsoft und nicht vom American National Standard Institute) https://de.wikipedia.org/wiki/Datei:Vorfenster_additional_window_fenetre_pour_l_hiver.JPG
  • 44. CP1252 / Windows-1252 ● Umlaute ● Euro = 0x80 ● Kann bei Anzeige auf 7 Bit Terminals zu Steuerzeichen führen ● Best practice: Wenn Dokument selbst sagt, dass es Latin-1 ist, dann CP1252 annehmen (zB W3C Recommendation in HTML5)
  • 45. Zusammenfassung 8 Bit Encodings Encoding Umlaute? Euro? ASCII Nein Nein Latin-1 Ja Nein Latin-9 Ja Ja, bei 0xa4 CP1252 Ja Ja, bei 0x80
  • 46. Empfehlungen für deutsche Texte ● Vorzugsweise UTF-8 verwenden ● Wenn 8 Bit Encoding erforderlich, vorzugsweise CP1252; falls nicht vorhanden, dann Latin-1 ● 8 Bit Encodings nur verwenden wenn: – Anwendung / Protokoll / System Unicode noch nicht unterstützt – Migration unwirtschaftlich ist – Speichereffizient kritisch ist
  • 48. EBCDIC ● „Extended Binary Coded Decimal Interchange Code“ ● Entstanden um 1963 ● Verwendet auf Großrechner ● Nicht verwenden außer externe Daten erfordern es.
  • 49. CP500 ● Zeichen für englische Texte (und einige Sonderzeichen) ● Bedingt Vergleichbar mit ASCII ● Encoding für Dateisystem. Gültige Zeichen in Namen: a-z, 0-9, $, @, TODO; Trennzeichen „.“, „(“ und „)“.
  • 50. CP500
  • 51. CP273 ● Für westeuropäische Länder ● Vergleichbar mit ISO-8859-1 / Latin-1
  • 52. CP1141 ● Ähnlich CP273 aber mit Euro-Zeichen ● Bedingt vergleichbar mit ISO-8859-15 und CP1252
  • 53. Empfehlungen für EBCDIC ● Wenn möglich: EBCDIC nicht verwenden ● Für Quelldaten: CP1141 ● Für Zieldaten: CP1252 ● Für Dateinamen: CP500 (Systemvorgabe)
  • 55. Wann en- und decoden ● Beim Einlesen Text decoden ● Programmintern immer alle Texte als Unicode ● Bei Ausgabe Text encoden Python- Programm Unicode Eingabe- Bytes Ausgabe- Bytes Decode Encode
  • 56. Ein- und Ausgabe für Text ● io.open(path, mode, encoding=...) # mode='r' oder 'w' ● csv.reader(file, ...) # Nutzt Encoding von file csv.writer(file, ...) ● xml.etree.ElementTree.parse(path) xml.etree.ElementTree.write( path, encoding=..., xml_declaration=True) ● io.StringIO # ließt / schreibt Unicode String
  • 57. Ein- und Ausgabe für Byte-Daten ● io.open(path, mode) # mode='rb' oder 'wb' ● io.BytesIO # ließt/schreibt Bytes ● Kompatibilität: io-Modul ist verfügbar seit Python 2.6+
  • 59. Python Default Encoding ● Unterschiedliches Ergebnis je nach Python Version, Betriebssystem, Mondphase und Tagesverfassung ● Empfehlung: ignorieren und immer explizites Encoding nutzen $ python2.6 -c "import sys; print(sys.getdefaultencoding())" ascii $ python3.4 -c "import sys; print(sys.getdefaultencoding())" utf-8
  • 60. Encoding für Terminal / Console ● Ähnlich hoffnungslos wie sys.getdefaultencoding() ● Unter Unix / Mac OS X: $ export LC_ALL=en_US.UTF-8 ● Unter Windows und cmd.exe: chcp und UTF-8 Hacks wie beschrieben in http://stackoverflow.com/questions/878972/ ● Print() i.d.R. ok aber nicht notwendigerweise logging-Modul auf sys.stderr, Ein-/Ausgabe mit sys.std*, Pipes, ... ● Unter Python 2 deterministisch praktisch immer kaputt, unter Python 3 je nach Version.
  • 62. Eingabe-Encoding 1.Dokumentation – oft einen Versuch wert 2.Beispieldaten analysieren 3.Heuristiken nutzen, zB – Unicode, Dammit: http://www.crummy.com/software/BeautifulSoup/bs4/do c/#unicode-dammit – Chardet: https://pypi.python.org/pypi/chardet
  • 63. Beispieldaten analysieren ● Grundstrategie für deutsche Texte ● Auf BOM prüfen: wenn vorhanden, dann je nach Ausprägung UTF-32, UTF-16 oder UTF-8 ● Als Hexdump anzeigen und auf Umlaut-ü und Euro- Zeichen prüfen
  • 64. Beispieldaten analysieren Zeichen Bytes Encoding ü 0xc3 0xbc UTF-8 ü 0xfc CP1252, Latin-1 oder Latin-9 € 0xe2 0x82 0xac UTF-8 € 0x80 CP1252 € 0xa4 Latin-9
  • 65. Beispiel 1 00000000: 46 61 6c 73 63 68 65 73 20 c3 9c 62 65 6e 20 76 |Falsches ..ben v| 0000000f: 6f 6e 20 58 79 6c 6f 70 68 6f 6e 6d 75 73 69 6b |on Xylophonmusik| 0000001f: 20 71 75 c3 a4 6c 74 20 6a 65 64 65 6e 20 67 72 | qu..lt jeden gr| 0000002f: c3 b6 c3 9f 65 72 65 6e 20 5a 77 65 72 67 20 66 |....eren Zwerg f| 0000003f: c3 bc 72 20 e2 82 ac 20 31 30 |..r ... 10|
  • 66. Beispiel-Pangram „Falsches Üben von Xylophonmusik quält jeden größeren Zwerg für € 10“
  • 67. Beispiel 1 00000000: 46 61 6c 73 63 68 65 73 20 c3 9c 62 65 6e 20 76 |Falsches ..ben v| 0000000f: 6f 6e 20 58 79 6c 6f 70 68 6f 6e 6d 75 73 69 6b |on Xylophonmusik| 0000001f: 20 71 75 c3 a4 6c 74 20 6a 65 64 65 6e 20 67 72 | qu..lt jeden gr| 0000002f: c3 b6 c3 9f 65 72 65 6e 20 5a 77 65 72 67 20 66 |....eren Zwerg f| 0000003f: c3 bc 72 20 e2 82 ac 20 31 30 |..r ... 10|
  • 68. Beispiel 2 00000000: 46 61 6c 73 63 68 65 73 20 dc 62 65 6e 20 76 6f |Falsches .ben vo| 0000000f: 6e 20 58 79 6c 6f 70 68 6f 6e 6d 75 73 69 6b 20 |n Xylophonmusik | 0000001f: 71 75 e4 6c 74 20 6a 65 64 65 6e 20 67 72 f6 df |qu.lt jeden gr..| 0000002f: 65 72 65 6e 20 5a 77 65 72 67 20 66 fc 72 20 80 |eren Zwerg f.r .| 0000003f: 20 31 30 | 10|
  • 69. Beispiel 2 00000000: 46 61 6c 73 63 68 65 73 20 dc 62 65 6e 20 76 6f |Falsches .ben vo| 0000000f: 6e 20 58 79 6c 6f 70 68 6f 6e 6d 75 73 69 6b 20 |n Xylophonmusik | 0000001f: 71 75 e4 6c 74 20 6a 65 64 65 6e 20 67 72 f6 df |qu.lt jeden gr..| 0000002f: 65 72 65 6e 20 5a 77 65 72 67 20 66 fc 72 20 80 |eren Zwerg f.r .| 0000003f: 20 31 30 | 10|
  • 71. UnicodeEncodeError >>> 'Spätzle: 10€'.encode('ascii') Traceback (most recent call last): File "examples.py", line 78, in <module> 'Spätzle: 10€'.encode('ascii') UnicodeEncodeError: 'ascii' codec can't encode character 'xe4' in position 2: ordinal not in range(128)
  • 72. UnicodeDecodeError >>> spaetzle_bytes = 'Spätzle: 10€'.encode('cp1252') >>> print(spaetzle_bytes) b'Spxe4tzle: 10x80' >>> spaetzle_bytes.decode('utf-8') Traceback (most recent call last): File "/Users/agi/workspace/talks/pygraz/unicode/examples.py", line 85, in <module> spaetzle_bytes.decode('utf-8') UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe4 in position 2: invalid continuation byte
  • 73. Was tun bei UnicodeError?
  • 74. Was tun bei UnicodeError? ● Jammern ● Weinen ● Brüllen ● Dinge durch den Raum werfen ● Schoklade essen ● Etc etc etc
  • 75. UnicodeError vermeiden ● Empfehlungen von vorhin folgen ● Früh decoden ● Intern nur Unicode ● Spät encoden ● Explizites Encoding angeben J. Robinson, Bomb Queen WMD – Woman of mass destruction, p. 7
  • 76. Was tun wenn doch UnicodeError? ● Daten vor (De-)Kodieren im Rohformat ausgeben: print('%r' % data) ● errors='replace' setzen und das Ergebnis analysieren ● encodings.cp1252 „verbessern“, so dass jedes Byte (de-)kodierbar ist und das Ergebnis analysieren. ● Alles Workarounds um Fehler zu finden → nach Lösungsfindung wieder abbauen und sauber umsetzen
  • 77. CP1252 „verbessern“ ● Nicht alle Bytes sind mapbar ● Einige mappen auf „undefined“ → UnicodeError decoding_table = ( 'x00' # 0x00 -> NULL 'x01' # 0x01 -> START OF HEADING 'x02' # 0x02 -> START OF TEXT ... '}' # 0x7D -> RIGHT CURLY BRACKET '~' # 0x7E -> TILDE 'x7f' # 0x7F -> DELETE 'u20ac' # 0x80 -> EURO SIGN 'ufffe' # 0x81 -> UNDEFINED 'u201a' # 0x82 -> SINGLE LOW-9 QUOTATION MARK 'u0192' # 0x83 -> LATIN SMALL LETTER F WITH HOOK 'u201e' # 0x84 -> DOUBLE LOW-9 QUOTATION MARK … )
  • 78. CP1255 „verbessern“ import codecs from encodings import cp1252 decoding_table = ''.join([ code if code != 'ufffe' else chr(index) for index, code in enumerate(cp1252.decoding_table) ]) assert 'ufffe' not in decoding_table cp1252.decoding_table = decoding_table cp1252.encoding_table = codecs.charmap_build(decoding_table) # Führt normalerweise zu UnicodeEncodeError. print('x81'.encode('cp1252'))
  • 80. Relevanz von Normalisierung ● In vielen Fällen keine Relevanz, insbesondere: – Daten kommen alle aus dem selben System (zB eine zentrale Datenbank) – Daten werden nur angezeigt oder ausgewertet ● Vor allem dann ein Thema, wenn: – Datenaustausch mit externen Systemen und unterschiedlichen Plattformen – Daten werden verglichen
  • 81. Gleichaussehende Zeichen Verschiedene Zeichen, die gleich aussehen: ● Großes I: U+0049 ● Römische 1: U+2160 >>> print('u0049') I >>> print('u2160') Ⅰ
  • 82. Verschiedene Darstellungen Verschiedene Darstellungen des selben Zeichen, zB Umlaut-ä: ● Ein Zeichen: U+00e4 ● zusammengesetztes Zeichen: U+0061 U+0308 ('a' + zwei Punkte darüber) print_hexdump([code for code in unicodedata.normalize('NFC', 'ä').encode('utf-16be')]) 00000000: 00 e4 |..| print_hexdump([code for code in unicodedata.normalize('NFD', 'ä').encode('utf-16be')]) 00000000: 00 61 03 08 |.a..|
  • 83. Normalisierung in Filesystem Keine Einheitlichkeit: Beispiel: with io.open('u0049.tmp', 'wb'): pass with io.open('u2160.tmp', 'wb'): pass Ergebnis mit Mac OS Extended Filesystem: $ ls -1 *.tmp I.tmp Ⅰ.tmp
  • 84. Vergleichen mit Normalisierung ● NFC - wenn inhaltlich gleiche Zeichen gleich sein sollen ● NFKC - wenn auch optisch gleiche Zeichen gleich sein sollen (Sicherheitsthema) ● NFD, NFKD – gleiches Ergebnis, aber erfordert unter Umständen mehr Speicher from unicodedata import normalize print(normalize('NFC', 'u00e4') == normalize('NFC', 'u0061u0308')) # True – umlaut ä print(normalize('NFC', 'u0049') == normalize('NFC', 'u2160')) # False – letter I and Roman number I print(normalize('NFKC', 'u00e4') == normalize('NFKC', 'u0061u0308')) # True – umlaut ä print(normalize('NFKC', 'u0049') == normalize('NFKC', 'u2160')) # True – letter I and Roman number I
  • 85. Empfehlungen zu Normalisierung ● Erst dann zum Problem machen, wenn es eines ist. ● Wenn möglich durchgängig einheitlich normalisieren (zB NFKC) ● Sonst je Eingangs-/Ausgangsystem beim Lesen/Schreiben die erforderliche Normalisierung nutzen ● Bei Dateinamen: abhängig vom Filesystem, für Python nicht feststellbar (zB NFS).
  • 87. Datentypen Python 2 und 3 Code Python 2.6+ Python 3.2+ 'x' str (8 Bit) str (Unicode) b'x' str (8 Bit) bytes (8 Bit) u'x' unicode (Unicode) str (Unicode)
  • 88. Python 2 wie Python 3 ● from __future__ import unicode_literals – '…' entspricht u'...' ● io.open() statt open() ● io.StringIO statt StringIO und cStringIO ● Wo Byte-Strings erforderlich explizit b'...' verwenden Beispiel: csv.reader(file, delimter=b';') ● Aber: manche Unterschiede bleiben bestehen, zB: – str() - unicode() – __str__ - __unicode__ – chr() – unichr() – Schmerzlinderung: six.*_types etc → https://pypi.python.org/pypi/six
  • 89. Unicode in Python 2 und 3 ● Ab 2.0: grundsätzlich unterstützt (u'text', codecs und unicodedata Modul) ● Ab 2.5: __future__.unicode_literals Modul ● Ab 2.6: io Modul ● Ab 3.0: durchgängig aber teilweise inkompatibel zu Python 2 unterstützt (kein u'text') ● Ab 3.3: wieder mit u'text'; verbesserte Erkennung für Terminal-Encoding
  • 90. Empfehlung ● Wenn möglich Python 3.3+ verwenden ● Sonst: wenn möglich Python 2.6+ verwenden ● Sonst: andere Programmiersprache mit guter Unicode-Implementierung verwenden (zB Java, C#, go, etc)
  • 92. Zusammenfassung ● Python unterstützt Unicode je nach Version in unterschiedlicher Qualität ● früh decoden, intern alles Unicode, spät encoden ● Encodings – fix setzen, nicht auf Automatismen hoffen – ggf. selbst herausfinden mit Hexdump, ü und € – besonders nützlich: UTF-8, CP1252 ● Python 2 Module für Unicode ähnlich Python 3: io, unicode_literals