Hilfe!!! ... bei Zeichensatz-Codierungen (UTF-8, 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
Graf_Dracula
User
Beiträge: 20
Registriert: Donnerstag 17. Februar 2011, 16:05

Ich habe in PortablePython_1.1_py3.0.1 ein Tool geschrieben, mit welchem ich innerhalb von Verzeichnissen und auch innerhalb von zip-Dateien nach text-Dateien und wiederum innerhalb dieser nach bestimmten Textstrings suchen kann.

Mein Problem ist nun folgendes: Ich nehme mal als Beispiel die folgende Textdatei:

test.txt
Auf meiner Festplatte unter 'C:\Programme' hab ich "tolle" Programme.
Ja Ok, der Text ist doof .. iss jetzt aber wurscht.

Beim debuggen kann ich sehen, dass wenn das Programm diese Textdatei einliest in der Variable das dann so ausieht:

fileString=
Auf meiner Festplatte unter 'C:\Programme' hab ich "tolle" Programme.
Packe ich diese Datei nun in eine zip ein und hole mir dann die Datei aus diesem zip mit Hilfe des mitgelieferten Moduls "zipfile", steht da folgendes in der Variablen:

fileString=
Auf meiner Festplatte unter \'C:\\Programme\' hab ich "tolle" Programme.
Will ich nun genau nach diesem String in der Datei suchen, sagt mir das Programm natürlich, dass die Datei innerhalb des zip NICHT den gesuchten String enthält. Meiner Meinung nach liegt das an irgendwelchen falschen Zeichensätzen und Codierungen ... aber das ist mir irgendwie im Moment bisserl zu kompliziert da durch zu steigen.

Was ich schon versucht habe ist:

Code: Alles auswählen

tmp1 = fileString.encode("iso-8859-1")
tmp2 = fileString.encode("utf-8")
Aber besser isses damit nicht geworden ... eher schlechter. Eigentlich müsste ich das glaub nen encoden, sondern decoden .... die Methode gibbed aber nicht in der Python-Version, zumindest bringen mir die folgenden Beiden zeilen immer diesen Fehler:

Code: Alles auswählen

tmp1 = fileString.decode("iso-8859-1")
tmp2 = fileString.decode("utf-8")
AttributeError: 'str' object has no attribute 'decode'

Was also kann/muss ich machen um den aus dem zip ausgelesenen String so zu konvertieren, dass er innerhalb der Variablen richtig dargestellt wird???


.
Benutzeravatar
snafu
User
Beiträge: 6736
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Das hat nichts mit dem Zeichensatz zu tun, sondern damit, dass diese Zeichen maskiert werden mussten, um nicht falsch interpretiert zu werden. Ein `print(fileString)` dürfte dir diejenige Version des Strings anzeigen, die du hier wohl erwartet hättest. Wenn du den Variablennhalt ohne `print()` in der Python-Shell anzeigst, dann wird nämlich implizit ein Aufruf von `repr(fileString)` gemacht (bzw ein `print(repr(fileString))`).

Zu dem Umwandlungsproblem: Strings in Python 3.x können halt nicht weiter dekodiert werden, das funktioniert dort nur auf Bytes. In Python 2.x ist es genau umgekehrt, da sind String nämlich noch Bytes und Unicode ist dort das, was unter Python 3.x ein String wäre. Die Thematik ist nervig, ich weiß. Aber mit wildem rumprobieren kommt man da auch nicht weiter. Mal abgesehen davon, dass du mit diesem Vorgehen in deinem Fall ohnehin auf dem falschen Dampfer bist. ;)
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Python 3.0.1 ist veraltet. Es gibt aber auch "Portable Python 3.2.0.1", wie ich gerade gesehen habe. Nimm lieber dies. Unabhängig davon musst du beachten, dass Python 3.x korrekt zwischen Byte-Arrays (bytes) und Strings (str) unterscheidet. Wenn du etwas aus einer Datei liest, ist es so lange ein Byte-Array bis du ein Encoding angegegen hast. Das macht die `open()`-Funktion leider automatisch, um dir zu helfen.

Mit `open(name)` wird implizit `open(name, "r")` und das Default-Platform-Encoding benutzt, was vielleicht das selbe Encoding ist, was die Datei hat oder auch nicht. Daher benutze immer `open(name, encoding="...")`, z.B. `encoding="cp1252"` unter Windows oder `encoding="utf8"` unter Linux und OS X.

Willst du das Byte-Array haben, benutze `open(name, "rb")`. Ein `bytes`-Objekt hat eine Methode `decode()`, mit der du daraus einen String machen kannst. Ein `str`-Objekt hat im Gegenzug eine `encode()`-Methode, um daraus ein Byte-Array zu machen, in dem die Zeichen in den angegeben Encoding kodiert werden.

Stefan
Graf_Dracula
User
Beiträge: 20
Registriert: Donnerstag 17. Februar 2011, 16:05

Ja nee ... eben ned ... wenn ich die Variablen mittels Print() ausgebe, steht genau der gleiche "maskierte" Quatsch drin. Selbst wenn er das beim Print() richtig machen würde, würde mir das trotzdem nix helfen, weil ich ja die Strings im Programm vergleichen will/muss. Die einzige Möglichkeit, die mir hier grad einfällt ist, in einem solchen String mit einer Extra Methode die Maskierungen zu entfernen, damit ich die Strings wieder miteinander vergleichen kann ... aber das scheint mir eine Augen-OP zu sein, bei der Man sich Zugang von Hinten durch den Rücken besorgt ... Das muss doch auch einfacher gehen ... nur wie ???
Graf_Dracula
User
Beiträge: 20
Registriert: Donnerstag 17. Februar 2011, 16:05

sma hat geschrieben:Python 3.0.1 ist veraltet. Es gibt aber auch "Portable Python 3.2.0.1"
OK, hab ich mir gleich mal geholt und nutze des jetzt ... merci
sma hat geschrieben:Daher benutze immer `open(name, encoding="...")`, z.B. `encoding="cp1252"` unter Windows oder `encoding="utf8"`
Das bringt immer nen error, iss aber auch klar, wenn ich mir die Methode im Modul zipfile anschaue ... da gibed gar keinen Parameter für's encoding ;-(

Code: Alles auswählen

    def open(self, name, mode="r", pwd=None):
        """Return file-like object for 'name'."""
        if mode not in ("r", "U", "rU"):
            raise RuntimeError('open() requires mode "r", "U", or "rU"')
        if pwd and not isinstance(pwd, bytes):
            raise TypeError("pwd: expected bytes, got %s" % type(pwd))
        if not self.fp:
            raise RuntimeError(
                  "Attempt to read ZIP archive that was already closed")
BlackJack

@Graf_Dracula: Vielleicht solltest Du dann mal verraten was Du genau machst. Denn ich bekomme zumindest vom `ZipFile` als Typ `bytes` zurück, die in der Zeichenkettendarstellung nicht nur die Backslashes enthalten, sondern auch ein 'b' als Präfix. Und die Objekte haben natürlich auch eine `decode()`-Methode, mit der man sie in Zeichenketten umwandeln kann:

Code: Alles auswählen

from zipfile import ZipFile

def main():
    archive = ZipFile('test.zip')
    data = archive.read('test.txt')
    print(data)
    print(data.decode('utf-8'))


if __name__ == '__main__':
    main()
Testlauf:

Code: Alles auswählen

$ python3 forum.py
b'Auf meiner Festplatte unter \'C:\\Programme\' hab ich "tolle" Programme.\n'
Auf meiner Festplatte unter 'C:\Programme' hab ich "tolle" Programme.
Graf_Dracula
User
Beiträge: 20
Registriert: Donnerstag 17. Februar 2011, 16:05

Hier mal vielleicht ganz grob der Reihenfolge nach, was das Programm tut (tun soll). Hab mal die "wichtigen" Zeilen rauskopiert:

test.zip:

Code: Alles auswählen

 - "1" - '2' - C:/1 - C:\1 - ü - ö - ä -

Code: Alles auswählen

import os
import zipfile

textPattern = "test"
zipFileToProcess = "C:" + os.sep + "test.zip"

myZip = zipfile.ZipFile(zipFileToProcess, "r")
for fileInfo in myZip.infolist():
     file = myZip.open(fileInfo, 'r')
     file = str(file.read())
     print(file)
     start = file.find(textPattern)
print(file) ergibt folgendes:

Code: Alles auswählen

b'\xef\xbb\xbf - "1" - \'2\' - C:/1 - C:\\1 - \xc3\xbc - \xc3\xb6 - \xc3\xa4 -'
Wenn ich im Debugmode durchlaufen lasse und mir den Inhalt von file anschaue steht da folgendes drin:

Code: Alles auswählen

b\'\\xef\\xbb\\xbf - "1" - \\\'2\\\' - C:/1 - C:\\\\1 - \\xc3\\xbc - \\xc3\\xb6 - \\xc3\\xa4 -\'
Graf_Dracula
User
Beiträge: 20
Registriert: Donnerstag 17. Februar 2011, 16:05

Graf_Dracula hat geschrieben:
myZip = zipfile.ZipFile(zipFileToProcess, "r")
for fileInfo in myZip.infolist():
file = myZip.open(fileInfo, 'r')
file = str(file.read())
print(file)
start = file.find(textPattern)
Habs grad bemerkt ... durch des str() davor, wandle ich die aus dem zip ausgelesene Bytewurst in einen String um und der macht dann intern natürlich nochmal überall diesen zusätzlichen Backslash davor, wodurch das dann so aussieht.

Das mit dem erst open() und dann read() mach ich, weil das file aus file = myZip.open(fileInfo, 'r') in einer anderen Funktion verarbeitet wird, die auch aus tar-Archiven sowie normale Textfile verarbeitet. Aber ich denke jetzt bekomm ich das hin ... hoffe ich zumindest :D
Benutzeravatar
snafu
User
Beiträge: 6736
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Richtig, `str()` dekodiert die Bytefolge nicht automatisch. Rufe einfach ein `.decode()` mit der Bezeichnung für die gewünschte Kodierung auf das `bytes()`-Objekt auf. Dann kommt automatisch eine "native" Zeichenkette raus.

Merke:

`.encode()` (="kodieren", "verschlüsseln") => komische Bytewurst
`.decode()` (="entschlüsseln") => lesbares Zeug

Die Technik dahinter ist ja nichts anderes als dass da quasi irgendwelche Zahlenwerte in zugehörige Zeichen (i.d.R. von einer nicht-englischen Sprache*) übersetzt werden. Über den Stream werden halt diese Bytefolgen (natürlich in Form von Bits) verschickt und der Empfänger kann sie dann wieder zu japanischen Schriftzeichen machen. :)

Wichtig ist halt, dass man die Kodierung der Datei kennt, da sonst die falsche "Übersetzungstabelle" benutzt wird und Murks bei rauskommt.

* Ja gut, das hat eigentlich nichts mit der Sprache bzw deren Zeichensatz zu tun. Bei englisch funktioniert es nur so ohne weiteres Zutun, well das zumindest in unseren Breitengraden dem System-Encoding entspricht und daher implizit zur Umwandlung genutzt wird.
Graf_Dracula
User
Beiträge: 20
Registriert: Donnerstag 17. Februar 2011, 16:05

OMGGGG ... harrrrzzzz ... es geht nur so halb ...

... mit string.decode('utf-8') macht er zwar aus \' nen ' und aus \\ nen \ ... aber beim Zeilenende passt das noch ned.

Lese ich ne Textdatei ein, ist ein Zeilenumbruch nur nen \n
Lese ich eine Text-Datei aus nem zip ist das Ende \r\n

Ist utf-8 also die falsche Codierung ???

Kann die Codierung von zip zu zip vllt. auch noch unterschiedlich sein ??? .... denn dann würde das ja bei ner anderen zip schon wieder ned mehr gehen .... man man man ...
BlackJack

@Graf_Dracula: Eine UTF-8-Dekodierung macht überhaupt nichts mit Backslashes. Also auch nicht aus \' ein ' oder aus \\ ein \. In Deinen Daten sind nur die Bytewerte für ' und \ enthalten, da sind gar keine Backslashes davor die entfernt werden müssten!

Die Kodierung des Zeilenendes hat nichts mit der Zeichensatzkodierung zu tun. Unter Windows kann das zum Beispiel davon abhängen, ob die Datei als Textdatei oder als Binärdatei geöffnet wurde. Bei Dateien die als Textdateien geöffnet wurden, wandelt Windows beim lesen '\r\n' in '\n' und beim schreiben '\n' in '\r\n' um. Ausserdem gibt es einen bestimmten Bytewert bei dem Windows das einlesen beendet, auch wenn danach noch Daten in der Datei stehen. Binärdateien muss man deshalb unbedingt auch im Binärmodus öffnen.

Und natürlich kann die Zeichensatzkodierung von Textdateien in ZIP-Archiven unterschiedlich sein. Genau so wie die Art wie ein Zeilenende kodiert ist. Das beides ist ZIP völlig egal. In so einem Archiv wird 1:1 der Dateiinhalt gespeichert und auch wieder ausgelesen. Und zwar im Binärmodus, denn nur da ist sichergestellt, dass Betriebssysteme wie Windows nichts an den Daten verändern.
Graf_Dracula
User
Beiträge: 20
Registriert: Donnerstag 17. Februar 2011, 16:05

BlackJack hat geschrieben: In so einem Archiv wird 1:1 der Dateiinhalt gespeichert und auch wieder ausgelesen. Und zwar im Binärmodus, denn nur da ist sichergestellt, dass Betriebssysteme wie Windows nichts an den Daten verändern.
Also arbeite ich am besten nur mit Binärdaten???

Wenn ja, hab ich jetzt das Problem, dass ich meinen Suchstring über ein tkinter-Textfeld einlese ... und da lese ich ja dank Windoofs immer im Format mit "nur" \n am Zeilenende ein. Entweder also ich speichere dieses Textfeld kurz in einer tmp-Datei zwischen und lese diese dann wieder im Binärformat aus, oder ich ersetze in dem String aus dem tkinter-Textfeld alle \n durch \r\n ... Beides kommt mir aber wieder wie eine Augen-OP durch den Rücken vor ... gibts nicht ne Möglichkeit so ein Textfeld binär auszulesen, oder ne ganz andere Idee noch?
BlackJack

@Graf_Dracula: Ob Du nur mit Binärdateien arbeiten willst, hängt davon ab was Du machen möchtest. Wenn Du mit Texten arbeitest, sind Textdateien und (Unicode-)Zeichenketten in der Regel Binärdateien und Bytes vorzuziehen.

Der Rückgabewert von `tkinter`-Textfeldern hat nichts mit Windows zu tun. Da sollte auf jeder Plattform das Zeilenende als '\n' kodiert sein. Du müsstest den Code einfach so schreiben, dass er mit beiden Varianten klar kommt. Das mag unschön sein, aber solange Du nicht garantieren kannst, das Zeilenenden nur in einer Variante kodiert werden, ist das die flexibelste Lösung.

Wenn Du die Textdateien nur als Bytes verarbeitest, und Daten aus GUI-Eingabefeldern für Vergleiche herangezogen werden, musst Du die Zeichenketten aus der GUI übrigens auch in Bytes umwandeln — und zwar mit der richtigen Zeichensatzkodierung. Zumindest wenn auch Zeichen ausserhalb vom ASCII-Wertebereich vorkommen können.
Antworten