Switch Applikation to unicode string

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
mcdaniels
User
Beiträge: 168
Registriert: Mittwoch 18. August 2010, 19:53

Hallo,
nachdem ich jetzt schon einige Zeit damit verbringe diverse Foreneinträge zu wälzen und dennoch keine Lösung gefunden habe, eröffne ich hier mal einen neuen Thread.

Das Modul sqlite3 kodiert,soweit ich gelesen haben, alles in UTF-8. Wenn ich nun bei meinen beiden Eingabefeldern (die in die sqlite3 DB geschrieben werden) Sonderzeichen (zb Umlaute) angebe läuft mir mein Programm in einen Programming Error.

Code: Alles auswählen

You must not use 8-bit bytestrings unless you use a text_factory that can interpret 8-bit bytestrings (like text_factory = str). It is highly recommended that you instead just switch your application to Unicode strings.
Ok, entweder kann ich also text_factory einsetzen, oder (besser) meine App auf Unicode umstellen.

Nur, wie stell ich selbige auf Unicode um?

In der DB sehen die gespeicherten Sätze so aus:

Code: Alles auswählen

[(u'Daniel', u'test'), (u'Roberta', u'kenne mich nicht aus')]


LG
Daniel
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Schau Dir mal die Links in meiner Sig an :-) (Bzw. lies sie Dir durch!)

Als erstes das wichtigste Lemma: Unicode != utf-8 Das hatte ich seinerzeit nicht wirklich kapiert und war deswegen ziemlich verwirrt.

Wichtig ist auch noch, dass es bei Strings Unterschiede zwischen Python2 und 3 gibt - aber Du benutzt augenscheinlich Python2.

Die Daten, die Du da angibst stehen aber so im Python-Quelltext ;-) In der DB steht ja kein Python-Unicode-String-Literal, also ein `u"..."`.

Du musst in Deinem Programm darauf achten, dass Du eben nur Unicode-Literale verwendest, dein Encoding-Cookie mit der wirklichen Kodierung der Datei übereinstimmt und externe Daten beim "Eintritt" in Dein Programm in Unicode dekodierst (`decode`).

Wenn Du noch an diesem Gästebuch-Programm werkelst, dann kommen z.B. Byte-Strings mittels `raw_input` in Dein Programm. Diese musst Du eben in Unicode wandeln. Vielleicht gibt es auch noch anderen Quellen - das war jetzt mal ein Schuss ins Blaue :-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
mcdaniels
User
Beiträge: 168
Registriert: Mittwoch 18. August 2010, 19:53

Mahlzeit!
Die Daten, die Du da angibst stehen aber so im Python-Quelltext
Stimmt natürlich! :oops:

Der Rest klingt kompliziert, aber ich werds mir anschauen. :mrgreen:

Ja, ich werkle noch immer am Gästebuch.

LG
Daniel
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Naja, so kompliziert ist es dann doch nicht.

Du musst Dir das so vorstellen: Überall in Deinem PC geistern Strings rum. Die sind alle kodiert, man sagt dazu auch Byte-String in Python. Z.B. ASCII, ISO-8859-1, LATIN-1, UTF-8, UTF-16, usw. Diese Encodings definieren in einer Tabelle, welcher Integerwert auf welches Zeichen verweist. Im ASCII-Zeichensatz verweist der Wert 65 bspw. auf das `A`.

Unicode (im Python-Sinne) kannst Du Dir nun vorstellen als eine spezielle Art von String, die *nur* während der Laufzeit eines Programmes im Speicher existiert. Du kannst das nicht serialisieren, ohne es explizit oder implizit in einen kodierten Zeichensatz zu wandeln (kleine "Lüge", aber nimm es mal so hin).

Daneben können im Speicher aber auch (kodierte) Byte-Strings existieren, das macht es "kompliziert". Es gibt (in Python2) also zwei Arten von Strings: Byte-Strings und Unicode-Strings.

Wenn Module nun interagieren sollen, wie eben Dein eigenes mit dem SQLite-Modul, ist es praktisch, dafür festzulegen, welche Art von String benutzt werden soll. Wählte man dafür Byte-Strings, so müsste man ja noch festlegen, welches Encoding! Davon gibt es leider so viele und nicht alle decken alle Zeichen ab... also ist es doch praktischer, Unicode-Strings zu "fordern". Damit obliegt es Dir, als "Benutzer" einer Lib, Deine Strings in Unicode zu wandeln, bevor Du die Funktionalität der Lib benutzen kannst.

Stell Dir das so ähnlich vor, wie bei der Erfindung des Geldes: Vorher tauschte man Gegenstände aus, die nicht immer zueinander passten - durch das Konzept des Geldes hat man ein universelles Gut entwickelt, in dem sich alle Geschäfte problemlos abwickeln lassen - theoretisch natürlich :-D

Unicode ist eben eine Art von String, die universell austauschbar ist. Daher solltest Du selber alle Deine Module ebenfalls (intern) auf Unicode umstellen. Probleme treten halt immer da auf, wo Daten von "irgend wo" außerhalb des Speichers auftauchen, also z.B. beim Laden aus einer Datei, beim Pipen aus einer Shell, bei der Eingabe durch den Benutzer, bei der Angabe von String-Literalen in Deinem Programm, usw. An diesen Stellen muss man die Byte-Strings dekodieren (= in Unicode-Strings wandeln).

Es gibt übrigens aus Module, die selber eben kein Unicode unterstützen, das Encoding nicht festlegen oder es nicht spezifizierbar machen, etwa das `csv`-Modul. Mit solchen Modulen hat man dann oftmals Ärger und muss einen Workaround bauen.

Vielleicht ist Dir nun schon aufgefallen, wo da das Problem liegt: Man muss das Encoding der Daten kennen - das macht es "mühsam" :-D Denn man kann das Encoding von Daten nicht deterministisch herausfinden, man kann maximal raten. Also ist es auch sinnvoll, bei den eigenen Schnittstellen eines Programms, zu spezifizieren, welche Art von Encoding erwartet bzw. akzeptiert wird. utf-8 wird von vielen deswegen gewählt, weil es (fast) den gesamten Unicode-Umfang abdeckt, d.h. Du kannst so ziemlich alle Zeichen in allen Sprachen verwenden. Bei ASCII hingegen hapert es bereits bei deutschen Umlauten und Sonderzeichen :-)

So, ich hoffe das war jetzt erhellend und nicht verstörend :-D
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

Hyperion hat geschrieben:Also ist es auch sinnvoll, bei den eigenen Schnittstellen eines Programms, zu spezifizieren, welche Art von Encoding erwartet bzw. akzeptiert wird. utf-8 wird von vielen deswegen gewählt, weil es (fast) den gesamten Unicode-Umfang abdeckt, d.h. Du kannst so ziemlich alle Zeichen in allen Sprachen verwenden.
Denkt UTF-8 nicht den gesamten UTF-8 Raum ab?

Ein weiterer Grund warum UTF-8 gerne genutzt wird ist, dass es zu ASCII kompatibel ist. Also ASCII ist auch valides UTF-8.
the more they change the more they stay the same
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Dav1d hat geschrieben: Denkt UTF-8 nicht den gesamten UTF-8 Raum ab?
Das dachte ich auch immer - aber irgend wer belehrte mich mal, dass es da irgend welche asiatischen Zeichen gäbe, die damit nicht abgedeckt würden... daher habe ich mich vorsichtig ausgedrückt ;-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
mcdaniels
User
Beiträge: 168
Registriert: Mittwoch 18. August 2010, 19:53

Hallo,
danke für die ausführliche Antwort.

Mein Encoding Cookie steht auf: # -*- coding: utf-8 -*-
Unter Idle speichere ich auch den Quellcode unter utf-8
Damit obliegt es Dir, als "Benutzer" einer Lib, Deine Strings in Unicode zu wandeln, bevor Du die Funktionalität der Lib benutzen kannst
Heißt, ich nehme die 2 Variablen, die die Eingabe des User enthalten her und wandle sie nach Unicode um. Erst dann kann ich das ganze in dieser Form in die DB schreiben.

Wenn ich UTF-8 habe muss ich also zuerst zu Unicode -> schreibe dann in die DB
Wenn ich dann Unicode in der DB hab muss ich wieder nach UTF-8 wenn ich die Daten korrekt angezeigt haben will

Das hier ist IMHO auch nicht so schlecht: http://xivilization.net/~marek/tex/unicode/unicode.pdf

LG
Daniel
Zuletzt geändert von mcdaniels am Mittwoch 6. Juni 2012, 13:09, insgesamt 2-mal geändert.
deets

Hyperion hat geschrieben:
Dav1d hat geschrieben: Denkt UTF-8 nicht den gesamten UTF-8 Raum ab?
Das dachte ich auch immer - aber irgend wer belehrte mich mal, dass es da irgend welche asiatischen Zeichen gäbe, die damit nicht abgedeckt würden... daher habe ich mich vorsichtig ausgedrückt ;-)
Dann hat jemand dich falsch belehrt. UTF-8 kann alle Zeichen, auch die ausserhalb zb der BMP encodieren.
mcdaniels
User
Beiträge: 168
Registriert: Mittwoch 18. August 2010, 19:53

Ganz klar ist mir das nicht...
@hyperion: Im Wiki Artikel der in deiner Signatur verlinkt ist steht wird mit iso8869-1 gearbeitet. Da ich davon ausgehen dass ich mit UTF-8 arbeite, muss ich dann ja von UTF-8 auf Unicode.

Da schepperts dann mit den Umlauten.
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

Bei mir "schepperts" nicht:

Code: Alles auswählen

In [1]: s = u'aäö²³'

In [2]: u = s.encode('utf-8')

In [3]: u
Out[3]: 'a\xc3\xa4\xc3\xb6\xc2\xb2\xc2\xb3'

In [4]: print u
aäö²³

In [5]: u.decode('utf-8')
Out[5]: u'a\xe4\xf6\xb2\xb3'

In [6]: print _
aäö²³
the more they change the more they stay the same
mcdaniels
User
Beiträge: 168
Registriert: Mittwoch 18. August 2010, 19:53

Direkt in der Konsole läufts, wenn ich aber per

Code: Alles auswählen

name = raw_input("Name: ")
Die Variable name "befülle" und diese dann in Unicode umwandle kann ich nicht (bzw. weiss nicht wie ich) per vorangestelltem u "einfach" auf unicode kommen würde. und mittels encode/decode funktioniert es nicht.
deets

*WAS* funktioniert nicht? Bekommst du eine Fehlermeldung? Bist do so guetig die uns mal zu zeigen?

UNd vorrangestellte u's helfen nur bei String-Literalen - also nicht bei Eingaben.
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

mcdaniels hat geschrieben:Die Variable name "befülle" und diese dann in Unicode umwandle kann ich nicht (bzw. weiss nicht wie ich) per vorangestelltem u "einfach" auf unicode kommen würde. und mittels encode/decode funktioniert es nicht.
Doch genau damit funktioniert es. Die Schwierigkeit ist herauszufinden, wie die Daten kodiert sind

Sehr oft funktioniert aber

Code: Alles auswählen

import sys
name = raw_input("Name: ").decode(sys.stdin.encoding)
mcdaniels
User
Beiträge: 168
Registriert: Mittwoch 18. August 2010, 19:53

Nochmals alles zur Aufklärung:
Wie ich gerade gesehen habe wirft sys.stdin.encoding eine cp1252 aus
mein encoding cookie steht aufgrund dessen nun auf # -*- coding: iso-8859-1 -*-
Idle auf locale-defined


Die Funktion, die damit arbeiten soll (ich hoffe es reicht ein Auszug, sonst post ich gerne alles...)

Code: Alles auswählen

def db_add_entry():
    db_action = db_connection.cursor()
    name = raw_input("Name: ")
    comment = raw_input("Kommentar: ")
    name_uni = name.decode("iso-8859-1")
    comment_uni = name.decode("iso-8859-1")
    db_action.execute('INSERT INTO guestbook VALUES(?,?)',
                      (name_uni, comment_uni))
    db_connection.commit()
In der DB landet bei Eingabe von öüä aber: öäü

Mach ich aber grundsätzlich das Selbe in der Konsole;

Code: Alles auswählen

name_s = "äöü"
name_uni=name_s.decode("iso-8859-1")
name_uni
erhalte ich korrekt: u'\xe4\xf6\xfc'
deets

Du hast das immer noch nicht verstanden wozu das Cookie ist: das Cookie bestimmt, wie die unicode-Literale (das sind die mit dem u vornedran) welche in deinem SOURCECODE stehen codiert sind. Das heisst, dass das Cookie auf demselben Wert stehen muss, den auch dein Editor beim speichern verwendet. Wie man das sicherstellt haengt vom Editor ab - der Emacs (an dessen Syntax sich das Cookie eh anlegt) macht's automatisch richtig, kA wie andere Editoren das machen.

Wenn du aber Daten ein & ausliest, dann bist *DU* verantwortlich dafuer, die richtigen En/Dekodings zu verwenden. Es ist voellig ok, dass die vom Source-code-cookie abweichen!

Wenn deine Shell eben cp1252 benutzt, dann musst du das als decoding verwenden, um auf unicode Objekte zu kommen.

deine name_uni-Dekodierungen sollten dann eben auch name.decode("cp1252") lauten. Und was dann in der DB landet ist auch richtig - das sieht nach UTF-8 aus. Es kommt dann wieder darauf an, wie du das

- rausholst
- darstellst

Deswegen sieht das erstmal nicht falsch aus.
mcdaniels
User
Beiträge: 168
Registriert: Mittwoch 18. August 2010, 19:53

Danke für deine zusätzlichen Erläuterungen!
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

deets hat geschrieben: Dann hat jemand dich falsch belehrt. UTF-8 kann alle Zeichen, auch die ausserhalb zb der BMP encodieren.
Gut zu wissen... woher kann ich das nur gehabt haben... ich finde den Thread leider nicht wieder :-(
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
mcdaniels
User
Beiträge: 168
Registriert: Mittwoch 18. August 2010, 19:53

Hallo nochmals,
mit cp1252 funktioniert es.
Hätte da wohl selbst auch dahinter kommen können... :roll: Hatte nur im Wiki gelesen, dass CP1252 eine erweiterte iso-8859-15 hab aber die 5 nicht gesehen bei 15.

Codepage muss also ÜBERALL die selbe stehen, Kompatibilität gibt es nicht. (Eingravier)

LG
Daniel
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

mcdaniels hat geschrieben: Hätte da wohl selbst auch dahinter kommen können... :roll: Hatte nur im Wiki gelesen, dass CP1252 eine erweiterte iso-8859-1 ist. Deshalb der
"Erweitert" meint vermutlich eher, dass es da noch mehr Zeichen gibt - aber nicht, dass die Werte eines Zeichens identisch sind; also eben nicht wie bei ASCII und utf-8. Stell Dir das wie eine Menge vor; eine andere Menge mag alle Elemente dieser Menge umfassen, aber die Ordnung der Elemente muss ja nicht identisch sein. Genau diese "Ordnung" brauchst Du aber dann, wenn Du einen Byte-String ohne Umkodierung in einem anderen Encoding verwenden willst. Bei ASCII und utf-8 passt das eben, bei anderen nicht...

Beim Quellcode-Encoding würde ich ehrlich gesagt auf utf-8 setzen; das ist bei Python3 dann Standard. Nur weil die Shell Deines jetzigen Systems ein anderen Encoding verwendet, würde ich nicht zwangsläufig deswegen das Encoding meines Quellcodes ändern ;-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Hyperion hat geschrieben:
deets hat geschrieben: Dann hat jemand dich falsch belehrt. UTF-8 kann alle Zeichen, auch die ausserhalb zb der BMP encodieren.
Gut zu wissen... woher kann ich das nur gehabt haben... ich finde den Thread leider nicht wieder :-(
UTF-8 und UTF-16 koennen alles abdecken, da sie eine variable Codelaenge benutzen, das Problem ist UCS-2, das _immer_ 2 Byte benutzt. Weil Python intern UCS-2 fuer unicode nutzt (nutzte?), zumindest in UCS-2 Builds, kann es _hier_ zu Problemen kommen.
Antworten