Erste Hilfekasten
für Unicode mit Python
Version 1.2
Thomas Aglassinger
https://github.com/roskakori/talks/tree/master/pyg...
Agenda
1.Wozu Unicode?
2.Unicode Zeichenketten in Python 3
3.Encodings
4.Wann Daten (de-)kodieren?
5.Vorhandenes Encoding ...
Ziele
Die Teilnehmer können...
● ...Texte mit Umlauten in Python verarbeiten.
● ...ein geeignetes Encoding wählen.
● ...di...
Nicht-Themen
● Verstehen der Encodings auf Bit-Ebene
→ Wikipedia
● Implementieren eigener Encodings
● Sicherheitsüberlegun...
Wozu Unicode?
● Zur Darstellung und Verarbeitung von Texten
● Deckt die meisten verwendeten Schriftzeichen ab
(dz. ca. 110...
Unicode ist ein Mapping
● Computer verarbeiten nur Zahlen
● Für Anzeige: Umwandlung der Zahlen in
Buchstaben und andere Ze...
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...
Hex-Notation
U+20ac
euro_sign = '€'
euro_sign = 'u20ac'
euro_sign = chr(0x20ac) # Python 2: unichr(0x20a0)
euro_sign = chr...
Unicode Zeichenketten
in Python 3
Unicode Zeichenketten
english_text = 'cheese spaetzle: 10 euro'
german_text = 'Käsespätzle: 10€'
german_text = u'Käsespätz...
Encodings
Encodings
● Externe Darstellung von Zeichenketten
● Für Datenaustausch über Dateien,
Netzwerkverbindungen etc
● Viele gebr...
Encodings
● Interne Darstellung von Unicode-Zeichen i.d.R. als
32 Bit Integer (4 Byte)
● Encodings stellen Zeichenketten i...
UTF-32 (UCS-4, ISO 10646)
● Ordnet jedem Unicode-Zeichen einen 32 Bit Integer
zu
● Per Definition zwischen 0 und 0x7ffffff...
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...
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...
UTF-32 (UCS-4, ISO 10646)
● Erfordert vergleichsweise viel
Speicherplatz
● Unterschiedliche Darstellung von 32
Bit Integer...
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...
Byte Order Mark
● 0xfeff0000 = „big endian“ (ARM, Motorola)
● 0x0000fffe = „little endian“ (Intel)
● Wenn kein BOM:
– Offi...
Byte Order Mark
● Vorsicht bei string.encode(): BOM wird bei jedem
Aufruf angehängt
>>> 'a'.encode('utf-32') + 'b'.encode(...
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 u...
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 ...
UCS-2
● Ähnlich UTF-16 aber nur für 0 bis 0xffff (16 Bit)
● „Jugendsünde“ von Unicode, als Chinesisch noch
als irrelevant ...
UTF-8
● Wandelt Zeichen um in 1 bis 4 Bytes
● Codes 0 bis 0x7f entsprechen den ASCII-Zeichen
● Codes 0x80 bis 0xff bedeute...
UTF-8
● Kann 0-Bytes enthalten: 0 bleibt 0.
● Variante: Modified UTF-8 (MUTF-8): wandelt 0 um
in 0xc0 0x80 → für C verwend...
UTF-8
● Kein BOM erforderlich, da sowohl auf big als auch
little endian das Ergebnis gleich ist
● Aber: BOM 0xefbbbf wird ...
UTF-8
● Sehr populär, verwendet für
– Python 3 Quellcodes (sofern nicht anders angegeben)
– XML (sofern nicht anders angeg...
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...
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
englischsprac...
ASCII
● Buchstaben A-Z, Ziffern 0-9, Satzzeichen
● Einige Sonderzeichen wie , {, }, $ etc
● Steuerzeichen (0x00 bis 0x1f)
...
ASCII
● Keine Umlaute
● Kein Euro
● Für praktisch alle Anwendungsfälle unbrauchbar
● ASCII-Texte „immer verarbeitbar“
● Of...
Latin-1 / ISO-8859-1
● Entstanden 1987
● Deckt die Anforderung vieler
westeuropäischen Länder ab als
„Never gonna give you...
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...
Latin-9 / ISO-8859-15
● Entstanden 1999, gleichzeitig mit Counter
Strike und der Euro-Einführung
● Weitgehend ident mit La...
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
...
CP1252 / Windows-1252
CP1252 / Windows-1252
● Umlaute
● Euro = 0x80
● Kann bei Anzeige auf 7 Bit Terminals zu
Steuerzeichen führen
● Best practi...
Zusammenfassung 8 Bit Encodings
Encoding Umlaute? Euro?
ASCII Nein Nein
Latin-1 Ja Nein
Latin-9 Ja Ja, bei 0xa4
CP1252 Ja ...
Empfehlungen für deutsche Texte
● Vorzugsweise UTF-8 verwenden
● Wenn 8 Bit Encoding erforderlich, vorzugsweise
CP1252; fa...
EBCDIC
EBCDIC
● „Extended Binary Coded Decimal Interchange
Code“
● Entstanden um 1963
● Verwendet auf Großrechner
● Nicht verwend...
CP500
● Zeichen für englische Texte (und einige
Sonderzeichen)
● Bedingt Vergleichbar mit ASCII
● Encoding für Dateisystem...
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 Date...
Wann Daten
en-/decoden?
Wann en- und decoden
● Beim Einlesen Text decoden
● Programmintern immer alle Texte als Unicode
● Bei Ausgabe Text encoden...
Ein- und Ausgabe für Text
● io.open(path, mode, encoding=...)
# mode='r' oder 'w'
● csv.reader(file, ...) # Nutzt Encoding...
Ein- und Ausgabe für Byte-Daten
● io.open(path, mode) # mode='rb' oder 'wb'
● io.BytesIO # ließt/schreibt Bytes
● Kompatib...
Python Default Encoding
Python Default Encoding
● Unterschiedliches Ergebnis je nach Python Version,
Betriebssystem, Mondphase und Tagesverfassung...
Encoding für Terminal / Console
● Ähnlich hoffnungslos wie sys.getdefaultencoding()
● Unter Unix / Mac OS X:
$ export LC_A...
Vorhandenes Encoding
ermitteln
Eingabe-Encoding
1.Dokumentation – oft einen Versuch wert
2.Beispieldaten analysieren
3.Heuristiken nutzen, zB
– Unicode, ...
Beispieldaten analysieren
● Grundstrategie für deutsche Texte
● Auf BOM prüfen: wenn vorhanden, dann je nach
Ausprägung UT...
Beispieldaten analysieren
Zeichen Bytes Encoding
ü 0xc3 0xbc UTF-8
ü 0xfc CP1252, Latin-1 oder
Latin-9
€ 0xe2 0x82 0xac UT...
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 ...
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 ...
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 ...
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 ...
UnicodeError
UnicodeEncodeError
>>> 'Spätzle: 10€'.encode('ascii')
Traceback (most recent call last):
File "examples.py", line 78, in <...
UnicodeDecodeError
>>> spaetzle_bytes = 'Spätzle: 10€'.encode('cp1252')
>>> print(spaetzle_bytes)
b'Spxe4tzle: 10x80'
>>> ...
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 En...
Was tun wenn doch UnicodeError?
● Daten vor (De-)Kodieren im Rohformat ausgeben:
print('%r' % data)
● errors='replace' set...
CP1252 „verbessern“
● Nicht alle Bytes sind mapbar
● Einige mappen auf „undefined“ → UnicodeError
decoding_table = (
'x00'...
CP1255 „verbessern“
import codecs
from encodings import cp1252
decoding_table = ''.join([
code if code != 'ufffe' else chr...
Unicode Normalisierung
Relevanz von Normalisierung
● In vielen Fällen keine Relevanz, insbesondere:
– Daten kommen alle aus dem selben System (zB...
Gleichaussehende Zeichen
Verschiedene Zeichen, die gleich aussehen:
● Großes I: U+0049
● Römische 1: U+2160
>>> print('u00...
Verschiedene Darstellungen
Verschiedene Darstellungen des selben Zeichen, zB Umlaut-ä:
● Ein Zeichen: U+00e4
● zusammenges...
Normalisierung in Filesystem
Keine Einheitlichkeit: Beispiel:
with io.open('u0049.tmp', 'wb'):
pass
with io.open('u2160.tm...
Vergleichen mit Normalisierung
● NFC - wenn inhaltlich gleiche Zeichen gleich sein sollen
● NFKC - wenn auch optisch gleic...
Empfehlungen zu Normalisierung
● Erst dann zum Problem machen, wenn es eines ist.
● Wenn möglich durchgängig einheitlich
n...
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' u...
Python 2 wie Python 3
● from __future__ import unicode_literals
– '…' entspricht u'...'
● io.open() statt open()
● io.Stri...
Unicode in Python 2 und 3
● Ab 2.0: grundsätzlich unterstützt (u'text', codecs und
unicodedata Modul)
● Ab 2.5: __future__...
Empfehlung
● Wenn möglich Python 3.3+ verwenden
● Sonst: wenn möglich Python 2.6+ verwenden
● Sonst: andere Programmierspr...
Zusammenfassung
Zusammenfassung
● Python unterstützt Unicode je nach Version in
unterschiedlicher Qualität
● früh decoden, intern alles Un...
Nächste SlideShare
Wird geladen in …5
×

Erste-Hilfekasten für Unicode mit Python

728 Aufrufe

Veröffentlicht am

Die Darstellung von Unicode-Zeichen in Python ist teilweise nicht ganz einfach nachvollziehbare Thematik. Diese Präsentation gibt Hilfstellungen, um den berüchtigten UnicodeError zu vermeiden. Behandelte Themen sind die Wahl eines Encodings, der richtige Zeitpunkt zum en- und decoden sowie die Erkennung eines verwendeten Encodings ohne entsprechende Dokumentation.

Veröffentlicht in: Technologie
0 Kommentare
0 Gefällt mir
Statistik
Notizen
  • Als Erste(r) kommentieren

  • Gehören Sie zu den Ersten, denen das gefällt!

Keine Downloads
Aufrufe
Aufrufe insgesamt
728
Auf SlideShare
0
Aus Einbettungen
0
Anzahl an Einbettungen
4
Aktionen
Geteilt
0
Downloads
1
Kommentare
0
Gefällt mir
0
Einbettungen 0
Keine Einbettungen

Keine Notizen für die Folie

Erste-Hilfekasten für Unicode mit Python

  1. 1. Erste Hilfekasten für Unicode mit Python Version 1.2 Thomas Aglassinger https://github.com/roskakori/talks/tree/master/pygraz/unicode
  2. 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. 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. 4. Nicht-Themen ● Verstehen der Encodings auf Bit-Ebene → Wikipedia ● Implementieren eigener Encodings ● Sicherheitsüberlegungen ● http://unicodesnowmanforyou.com/ ● 'U0001f4a9'
  5. 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. 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
  7. 7. Hex-Notation
  8. 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. 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')
  10. 10. Unicode Zeichenketten in Python 3
  11. 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'
  12. 12. Encodings
  13. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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
  29. 29. UTF-8 Diskriminierend: Amerikaner benötigen weniger Speicher als zB Chinesen
  30. 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. 31. 8 Bit Encodings ● Aus der Zeit vor Unicode ● Effizient: – Immer 1 Byte pro Zeichen – Ermittlung der Länge O(1)
  32. 32. 8 Bit Encodings: Herausforderungen ● Umlaute, scharfes S: ä ö ü ß Ä Ö Ü ● Euro: €
  33. 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. 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. 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. 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
  37. 37. Latin-1 / ISO-8859-1
  38. 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. 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
  40. 40. Latin-9 / ISO-8859-15
  41. 41. Latin-9 / ISO-8859-15 ● 0x08 bis 0x9f weiterhin unbenutzt (wegen 7 Bit Terminals) ● „nicht ganz“ kompatibel mit Latin-1
  42. 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
  43. 43. CP1252 / Windows-1252
  44. 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. 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. 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
  47. 47. EBCDIC
  48. 48. EBCDIC ● „Extended Binary Coded Decimal Interchange Code“ ● Entstanden um 1963 ● Verwendet auf Großrechner ● Nicht verwenden außer externe Daten erfordern es.
  49. 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. 50. CP500
  51. 51. CP273 ● Für westeuropäische Länder ● Vergleichbar mit ISO-8859-1 / Latin-1
  52. 52. CP1141 ● Ähnlich CP273 aber mit Euro-Zeichen ● Bedingt vergleichbar mit ISO-8859-15 und CP1252
  53. 53. Empfehlungen für EBCDIC ● Wenn möglich: EBCDIC nicht verwenden ● Für Quelldaten: CP1141 ● Für Zieldaten: CP1252 ● Für Dateinamen: CP500 (Systemvorgabe)
  54. 54. Wann Daten en-/decoden?
  55. 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. 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. 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+
  58. 58. Python Default Encoding
  59. 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. 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.
  61. 61. Vorhandenes Encoding ermitteln
  62. 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. 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. 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. 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. 66. Beispiel-Pangram „Falsches Üben von Xylophonmusik quält jeden größeren Zwerg für € 10“
  67. 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. 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. 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|
  70. 70. UnicodeError
  71. 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. 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. 73. Was tun bei UnicodeError?
  74. 74. Was tun bei UnicodeError? ● Jammern ● Weinen ● Brüllen ● Dinge durch den Raum werfen ● Schoklade essen ● Etc etc etc
  75. 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. 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. 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. 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'))
  79. 79. Unicode Normalisierung
  80. 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. 81. Gleichaussehende Zeichen Verschiedene Zeichen, die gleich aussehen: ● Großes I: U+0049 ● Römische 1: U+2160 >>> print('u0049') I >>> print('u2160') Ⅰ
  82. 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. 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. 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. 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).
  86. 86. Unicode Zeichenketten in Python 2
  87. 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. 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. 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. 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)
  91. 91. Zusammenfassung
  92. 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

×