SafeConfigParser und unicode

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Vortex
User
Beiträge: 16
Registriert: Sonntag 13. August 2006, 14:12
Kontaktdaten:

Hallo zusammen,

ich benutze in meinem Programm den SafeConfigParser von python um eine Konfigurationsdatei zu verwalten. In der Refernzdokumentation steht:
set( section, option, value)
If the given section exists, set the given option to the specified value; otherwise raise NoSectionError. value must be a string (str or unicode); if not, TypeError is raised. New in version 2.4.
Ein Wert in der Config-Datei kann also entweder ein String oder Unicode-Objekt sein. Ich nahm deshalb an, dass der SafeConfigParser keine Probleme mit Sonderzeichen hat.

Aber falsch gedacht, wenn der Wert einer Konfigurationsvariable Sonderzeichen enthält, dann stirbt mir mein SafeConfigParser beim speichern der Config-Datei:

Code: Alles auswählen

  File "/usr/lib64/python2.4/ConfigParser.py", line 372, in write
    fp.write("%s = %s\n" %
UnicodeEncodeError: 'ascii' codec can't encode character u'\u7a7a' in position 19: ordinal not in range(128)
Ich will meine Config-Datei nicht in ascii sondern UTF-8 abspeichern, geht das irgendwie?

Ich kann in meinem Programm nicht verhindern, dass Strings mit nicht-Standard-Zeichen gespeichert werden müssen und brauche diese Funktionalität daher unbedingt.

Vielen Dank schonmal im voraus. :D
BlackJack

Entweder (de)kodierst Du die Werte selbst beim eintragen oder auslesen in das Config-Objekt, oder Du benutzt Dateien die mit `codecs.open()` geöffnet wurden.
Vortex
User
Beiträge: 16
Registriert: Sonntag 13. August 2006, 14:12
Kontaktdaten:

Danke für den Tipp, das Laden klappt jetzt schonmal:

Code: Alles auswählen

def readConfig(self):
        self.readfp(codecs.open(self.defaultConfigPath, "r", "UTF-8"))
        try:
            self.readfp(codecs.open(self.userConfigPath, "r", "UTF-8"))
        except IOError:
            pass
Das Speichern mach ich folgendermaßen:

Code: Alles auswählen

def saveConfig(self):
        configFile = codecs.open(self.userConfigPath, "w", "UTF-8")
        self.write(configFile)
        configFile.flush()
Aber hier erhalte ich immernoch den hier:

Code: Alles auswählen

  File "/home/ich/workspace/Spring-collections1/config_manager.py", line 28, in saveConfig
    self.write(configFile)
  File "/usr/lib64/python2.4/ConfigParser.py", line 372, in write
    fp.write("%s = %s\n" %
UnicodeEncodeError: 'ascii' codec can't encode character u'\u7a7a' in position 11: ordinal not in range(128)
Was mache ich falsch?
Ich hab mich mit den Encodings und so Zeugs bisher nicht sonderlich stark beschäftigt, weil man das meistens in python ja recht einfach mit den unicode-Objekten handhaben kann.
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Vortex hat geschrieben:In der Refernzdokumentation steht:
value must be a string (str or unicode)
Hallo Vortex!

Ich habe mal ein wenig getestet:

Code: Alles auswählen

>>> import ConfigParser
>>> ini = ConfigParser.SafeConfigParser()
>>> ini.add_section(u"Über den Wolken...")
>>> ini.set(u"Über den Wolken...", u"...muss (ß) die Freiheit", u"wohl überaus grenzenlos sein.")
>>> import StringIO
>>> f = StringIO.StringIO()
>>> ini.write(f)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "J:\Python25\Lib\ConfigParser.py", line 373, in write
    (key, str(value).replace('\n', '\n\t')))
UnicodeEncodeError: 'ascii' codec can't encode character u'\xfc' in position 5: ordinal not in range(128)
>>>
Wenn der ConfigParser wirklich mit Unicode umgehen könnte, dann dürfte dieser Fehler nicht passieren. Die Zeile ``(key, str(value).replace('\n', '\n\t')))`` ist in meinen Augen ein Fehler im ConfigParser.

So wie es aussieht, kann der ConfigParser doch nur mit Bytestrings etwas anfangen. Unicode funktioniert nicht. :roll:

Wenn du deine INI-Datei unbedingt im UTF-8-Encoding haben möchtest, dann musst du dich selber darum kümmern, dass alles nach UTF-8 umgewandelt wird. Und das noch bevor es an die Funktionen des ConfigParsers übergeben wird.

Code: Alles auswählen

>>> import ConfigParser
>>> import StringIO
>>> ini = ConfigParser.SafeConfigParser()
>>> ini.add_section(u"Über den Wolken...".encode("utf-8"))
>>> ini.set(u"Über den Wolken...".encode("utf-8"), 
...     u"...muss die Freiheit".encode("utf-8"), 
...     u"wohl überaus grenzenlos sein. (öäüß)".encode("utf-8"))
>>> 
>>> f = StringIO.StringIO()
>>> ini.write(f)
>>> print f.getvalue()
[Ãœber den Wolken...]
...muss die freiheit = wohl überaus grenzenlos sein. (öäüß)

>>> ff = file(r"J:\Ablage\xxx.ini", "w")
>>> ff.write(f.getvalue())
>>> ff.close()
>>> 
EDIT:

Das Auslesen muss dann natürlich auch in UTF-8 geschehen:

Code: Alles auswählen

>>> ini.sections()
['\xc3\x9cber den Wolken...']
>>> ini.options(u"Über den Wolken...".encode("utf-8"))
['...muss die freiheit']
>>> ini.get(u"Über den Wolken...".encode("utf-8"), u"...muss die freiheit".encode("utf-8"))
'wohl \xc3\xbcberaus grenzenlos sein. (\xc3\xb6\xc3\xa4\xc3\xbc\xc3\x9f)'
>>>
mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Vortex
User
Beiträge: 16
Registriert: Sonntag 13. August 2006, 14:12
Kontaktdaten:

Sorry, dass ich mich erst jetzt melde, aber eine Frage hab ich noch:

Wenn ich jetzt die Config-Einträge von Hand nach UTF-8 konvertiere, muss ich dann die Konfigurationsdatei immernoch mit codecs.open und dem UTF-8 encoding öffnen?

Achja nochwas, ich brauche die Unterstützung für Sonderzeichen nur bei den Werten, nicht bei den Keys, die kann ich ja selbst festlegen. Ich muss nur bestimmte vom Benutzer eingegebene Daten in der Konfiguration speichern und da sollte der halt Sonderzeichen unterstützen.
Vortex
User
Beiträge: 16
Registriert: Sonntag 13. August 2006, 14:12
Kontaktdaten:

Bah, ich versteh gar nichts mehr. Es funktioniert einfach nicht, entweder kann er die Config nicht speichern oder danach nicht mehr laden.

Ich peil das auch einfach nicht mit den Codecs. Wer kann mir zum Beispiel das erklären:

Code: Alles auswählen

>>> bla ="äöü"
>>> bla = u"äöü"
>>> bla = unicode("äöü")
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 0: ordinal not in range(128)
Ich hab also versucht vor dem Speichern der Config alle Konfigurationswerte zu encoden:

Code: Alles auswählen

set(context, str(key), unicode(value).encode("utf-8"))
Aber wenn ich danach die Config speichern will:

Code: Alles auswählen

def saveConfig(self):
        configFile = open(self.userConfigPath, "w")
        self.write(configFile)
        configFile.flush()
krieg ich wieder:

Code: Alles auswählen

  File "/home/ich/workspace/Spring-collections1/config_manager.py", line 34, in saveConfig
    self.write(configFile)
  File "/usr/lib64/python2.4/ConfigParser.py", line 372, in write
    fp.write("%s = %s\n" %
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe7 in position 18: ordinal not in range(128)
Aber jetzt kommt der Hammer. Sobald ich das laden der Konfigurationsdatei (was ja davor stattfindet) von

Code: Alles auswählen

def readConfig(self):
        self.readfp(codecs.open(self.defaultConfigPath, "r", "UTF-8"))
        try:
            self.readfp(codecs.open(self.userConfigPath, "r", "UTF-8"))
        except IOError:
            pass
in folgendes ändere:

Code: Alles auswählen

def readConfig(self):
        self.readfp(open(self.defaultConfigPath, "r"))
        try:
            self.readfp(open(self.userConfigPath, "r"))
        except IOError:
            pass
dann kann er die Config plötzlich speichern.

Also nochmal langsam:
Wenn ich die Config-Datei beim einlesen der Config mit codecs.open öffne, dann kann er sie danach nicht speichern, obwohl ich sie zum Speichern erneut öffne und obwohl sämtliche Config-Werte neu gesetzt werden und mit encode("utf-8") encodiert werden.

Öffne ich die Config-Datei beim einlesen dagegen mit dem normalen open(), dann kann er sie auch speichern.

Jedoch kann ich sie danach nicht mehr lesen. Sobald ich einen Config-Wert in einem Unicode-Objekt speichern will, also etwa so:

Code: Alles auswählen

unicode(config.get(section, key))
erhalte ich folgenden Fehler:

Code: Alles auswählen

UnicodeDecodeError: 'ascii' codec can't decode byte 0xe7 in position 18: ordinal not in range(128)
Ich peil nix mehr. :roll:

EDIT: Achja nochwas, es werden zur Laufzeit keine neuen Sections oder Keys der Config-Datei hinzugefügt. Beim einlesen der Default Config-Datei werden sämtliche Konfigurationswerte auf einen Default-Wert gesetzt. Diese Werte werden dann zur Laufzeit verändert aber es werden keine neuen Schlüssel hinzugefügt.
Trotzdem dürfte doch das einlesen der Config-Datei nicht mit dem Speichern zusammenhängen, zumal ich Datei doch zweimal öffne und sämtliche Config-Werte vor dem Speichern mit encode("utf-8") encodiere. :?:
BlackJack

Vortex hat geschrieben:Ich peil das auch einfach nicht mit den Codecs. Wer kann mir zum Beispiel das erklären:

Code: Alles auswählen

>>> bla ="äöü"
>>> bla = u"äöü"
>>> bla = unicode("äöü")
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 0: ordinal not in range(128)
"äöü" enthält Bytewerte ausserhalb des ASCII-Bereichs deshalb musst Du angeben wie die Kodiert sind um eine Unicode-Zeichenkette daraus zu machen. Bei der zweiten Zeile ist übrigens in Quelltexten so noch nicht garantiert, dass da wirklich die drei Buchstaben "äöü" in der Unicode-Zeichenkette stehen. Das ist nur der Fall, wenn die angegebene Kodierung im "Kodierungskommentar" auch mit der tatsächlichen Kodierung des Quelltextes übereinstimmt.
Ich hab also versucht vor dem Speichern der Config alle Konfigurationswerte zu encoden:

Code: Alles auswählen

set(context, str(key), unicode(value).encode("utf-8"))
Wenn `value` nicht schon eine Unicode-Zeichenkette ist, dann kann `value` nur aus ASCII-Zeichen bestehen und der ganze Zauber ist komplett überflüssig. Reine ASCII-Zeichenketten verändern sich nicht, wenn man sie UTF-8 kodiert weil ASCII eine Untermenge von UTF-8 ist.
Aber wenn ich danach die Config speichern will:

Code: Alles auswählen

def saveConfig(self):
        configFile = open(self.userConfigPath, "w")
        self.write(configFile)
        configFile.flush()
krieg ich wieder:

Code: Alles auswählen

  File "/home/ich/workspace/Spring-collections1/config_manager.py", line 34, in saveConfig
    self.write(configFile)
  File "/usr/lib64/python2.4/ConfigParser.py", line 372, in write
    fp.write("%s = %s\n" %
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe7 in position 18: ordinal not in range(128)
Aber jetzt kommt der Hammer. Sobald ich das laden der Konfigurationsdatei (was ja davor stattfindet) von

Code: Alles auswählen

def readConfig(self):
        self.readfp(codecs.open(self.defaultConfigPath, "r", "UTF-8"))
        try:
            self.readfp(codecs.open(self.userConfigPath, "r", "UTF-8"))
        except IOError:
            pass
in folgendes ändere:

Code: Alles auswählen

def readConfig(self):
        self.readfp(open(self.defaultConfigPath, "r"))
        try:
            self.readfp(open(self.userConfigPath, "r"))
        except IOError:
            pass
dann kann er die Config plötzlich speichern.
Wenn Du mit `codecs` öffnest, dann enthält das `ConfigParser`-Objekt Unicode-Zeichenketten. Und damit kommt es beim Speichern nicht klar. Die Erkenntnis hatten wir doch schon einmal, oder?
Also nochmal langsam:
Wenn ich die Config-Datei beim einlesen der Config mit codecs.open öffne, dann kann er sie danach nicht speichern, obwohl ich sie zum Speichern erneut öffne und obwohl sämtliche Config-Werte neu gesetzt werden und mit encode("utf-8") encodiert werden.
Du setzt wahrscheinlich nur alle *Werte* neu, die Schlüssel werden vom einlesen her immer noch Unicode-Objekte sein. Und dann schau Dir mal die beanstandete Zeile an: Da werden Schlüssel und Wert in eine Zeichenkette formatiert. Sowie dort auch nur eine Unicode-Zeichenkette beteiligt ist, ist das Ergebnis auch eine. Dazu müssen aber normale Zeichenketten dekodiert werden, und dabei dürfen dann keine Zeichen ausserhalb von ASCII enthalten sein. Sind aber wenn Du zum Beispiel ein 'ä' als UTF-8 kodierst.
Öffne ich die Config-Datei beim einlesen dagegen mit dem normalen open(), dann kann er sie auch speichern.

Jedoch kann ich sie danach nicht mehr lesen. Sobald ich einen Config-Wert in einem Unicode-Objekt speichern will, also etwa so:

Code: Alles auswählen

unicode(config.get(section, key))
erhalte ich folgenden Fehler:

Code: Alles auswählen

UnicodeDecodeError: 'ascii' codec can't decode byte 0xe7 in position 18: ordinal not in range(128)
Weil da Bytes ausserhalb vom ASCII-Zeichensatz enthalten sind. Ich glaube ich wiederhole mich. :-)

Du musst halt sagen in welcher Kodierung die Daten vorliegen. Das lässt sich nicht automatisch ermitteln, darum nimmt Python einfach ASCII an und meckert, wenn etwas anderes kommt.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Vortex hat geschrieben:Ich peil das auch einfach nicht mit den Codecs. Wer kann mir zum Beispiel das erklären:

Code: Alles auswählen

>>> bla ="äöü"
>>> bla = u"äöü"
>>> bla = unicode("äöü")
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 0: ordinal not in range(128)
Im ersten Beispiel hast du einen Byte-String. Im zweiten einen Unicode-String der mit dem Standardencoding dekodiert wurde. Im dritten versuchst du einen Bytestring mit dem ASCII-Codec zu dekodieren, was natürlich fehlschlägt, weil ASCII keine Umlaute kennt. Also musst du ``unicode()`` als zweiten Parameter das Encoding angeben, das der Bytestring hat, also etwa ``latin-1``.

Code: Alles auswählen

In [57]: "üöä".decode('latin-1') == unicode("üöä", 'latin-1')
Out[57]: True
Also wenn du Bytestrings in Unicode konvertierst, musst du nunmal das Encoding angeben, da führt kein Weg vorbei - man kann das Encoding ja nur schwer erraten.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Hallo Vortex!

Vielleicht bringt dir diese Lektüre etwas:
http://www.python-forum.de/topic-5095.html

Mische nicht. -- Wenn die Werte UTF-8 sind, dann sollten evt. die Schlüssel und Sektionen auch UTF-8-codiert werden. (nur zur Sicherheit)
Die Hauptsache ist, dass der ConfigParser nur mit Bytecode-Strings zu tun hat und nicht mit Unicode. Wie wir ja inzwischen wissen, kann ConfigParser mit Unicode nicht umgehen.

mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Vortex
User
Beiträge: 16
Registriert: Sonntag 13. August 2006, 14:12
Kontaktdaten:

*heul*

Leute, es tut mir leid wenn ich so begriffsstutzig bin, aber ich hab das Gefühl ich versteh kein Wort von dem was ihr mit erklären wollt.

Ich soll also Schlüssel und Werte utf-8 kodieren, aber der ConfigParser darf nicht mit unicode arbeiten. ... ?
Was bedeutet das überhaupt? Warum muss ich die Schlüssel und Werte utf-8 kodieren, wenn ich die Config-Datei nicht im utf-8 encoding abspeichern darf? Sobald ich sie mit codecs.open öffne klappts ja nicht mehr.

Ich will ja im Prinzip nur, dass beim abspeichern alle Strings mit Sonderzeichen in ein ascii oder was-weiß-ich-was kompatibles Format encodiert werden, sodass der ConfigParser damit klarkommt.
Beim laden soll dann aber natürlich wieder die ursprüngliche Zeichenkette aus dem encodierten hergestellt werden.

Ich wäre froh, wenn mir jemand einfach folgende Punkte erklären könnte:

Wie muss ich die Config-Datei zum schreiben und lesen öffnen?
Welches encoding muss die Config-Datei haben?

Wie muss ich die Config-Werte (per get etc.) abfragen, damit ich sie in meinem Programm korrekt genutzt werden können. Muss ich da auch encode oder sowas verwenden? Oder muss ich einmal beim speichern encode und beim laden decode verwenden?

Wie muss ich die Config-Werte (mit set) setzen, damit sie korrekt gespeichert werden? Die Config-Werte sind normal alles unicode-Objekte.

EDIT: Leute, ich glaub ich habs geschafft. :D

Hab mir die Anleitung von gerold nochmal durchgelesen und meine grauen Zellen angestrengt.

Ich öffne die Datei zum Lesen mit:

Code: Alles auswählen

open(self.userConfigPath, "r")
bzw. zum schreiben mit:

Code: Alles auswählen

open(self.userConfigPath, "w")
Die Config-Werte setze ich mit:

Code: Alles auswählen

manager.set(context, str(key), unicode(value).encode("utf-8"))
Und einlesen tu' ich sie mit:

Code: Alles auswählen

manger.get(context, str(key), value.decode("utf-8"))
Soweit funktionierts. Alle strings werden gespeichert und nach dem laden wieder korrekt dargestellt.

Vielen Dank für eure Hilfe! :)

Eine Frage hätte ich aber noch: In welchem encoding liegt die Config-Datei denn jetzt vor? Also mit welchem encoding müsste man sie in einem Editor öffnen, damit alle gespeicherten Sonderzeichen korrekt dargestellt werden?
BlackJack

Es ist doch ganz einfach: `ConfigParser` mag keine Unicode-Zeichenketten. Also darf man die Datei nicht mit `codecs.open()` öffnen, weil diese Datei-Objekte Unicode-Zeichenketten liefern und erwarten. `ConfigParser`-Objekte sollten nur Byteketten enthalten, sonst kracht's, wie man ja gesehen hat.

Wenn Du Unicode-Objekte in der Konfiguration speichern willst, dann musst Du sie vor dem "hinein tun" in den `ConfigParser` als Bytekette kodieren und nach dem "herausholen" wieder zu Unicode dekodieren. Da bietet sich UTF-8 an, weil damit alle Unicode-Zeichen kodiert werden können.

Du solltest den Beitrag, den gerold verlinkt hat durcharbeiten.
BlackJack

Habe das "Edit" jetzt erst gesehen. Wenn Du alles als UTF-8 kodierst, dann rate mal in welcher Kodierung die Datei vorliegt. ;-)

Eventuell problematisch ist noch die Behandlung von `key`, es sei denn Du kannst sicher sein, dass `key` nie etwas ausserhalb von ASCII enthält, und der `unicode()`-Aufruf beim setzen ist überflüssig falls es sich sowieso schon um Unicode-Objekte handelt.
Antworten