TypeError: coercing to Unicode: need string or buffer. ???

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.
Benutzeravatar
akis.kapo
User
Beiträge: 127
Registriert: Freitag 1. September 2006, 12:58

TypeError: coercing to Unicode: need string or buffer. ???

Beitragvon akis.kapo » Donnerstag 17. Januar 2008, 13:54

Hallo zusammen,

kann mir bitte jemand auf die Sprünge helfen?

Ich komme nicht weiter und dreh mich nur noch im Kreis mit der Problematik.

Folgendes Programm:

myfindduplicates.py

Code: Alles auswählen

#!/usr/bin/python
from __future__ import with_statement
import os
import md5
import sys

def fsize(file):
    return os.stat(file)[6]

def fmd5sum(file):
    return md5.new(file.read()).digest()

def mkprimkey(file):
    output = set()
    output.add(fsize(file))
    output.add(fmd5sum(file))
    return output

def addentry(dict, pkey, fname):
    if dict.has_key(pkey):
        dict[pkey].add(fname)
    else:
        valset = set()
        dict[pkey] = valset.add(fname)

### main programm ###

mydict = dict()

for fname in sys.stdin:
    fname = fname.split('\n')[0]
    with open(fname) as file:
        pkey = mkprimkey(file)
        addentry(mydict, pkey, fname)

print mydict


Aufgerufen wird das ganze in einem Ordner mit 11 Dateien, wobei 10 verschieden sind und eine Datei nur die Kopie einer anderen ist mit anderem Namen. Das Kommando des Programmstarts sieht so aus:

# find . -maxdepth 1 -type f -name "*.py" -printf "%f\n" | ./myfindduplicates.py

Es kommt zu folgender Fehlermeldung, mit der ich nichts anfangen kann:

Code: Alles auswählen

Traceback (most recent call last):
  File "./fsizemd5sum.py", line 40, in <module>
    pkey = mkprimkey(file)
  File "./fsizemd5sum.py", line 22, in mkprimkey
    output.add(fsize(file))
  File "./fsizemd5sum.py", line 15, in fsize
    return os.stat(file)[6]
TypeError: coercing to Unicode: need string or buffer, file found


Kann jemand was intelligentes dazu sagen? Ich steh grad aufm Schlauch.
BlackJack

Beitragvon BlackJack » Donnerstag 17. Januar 2008, 14:17

`os.stat()` erwartet einen Datei*namen* und kein Datei*objekt*.

`fsize()` gibt's schon: `os.path.getsize()`.

Die gesamte Datei in den Speicher zu lesen um die Prüfsumme zu berechnen, kann ungemütlich werden, wenn man das in einem Ordner mit Filmen, CD/DVD-Images, o.Ä. macht.

Ein `set()` kann man nicht als Schlüssel verwenden, da nicht "hashable". Da würde man eher ein Tupel verwenden.

`addentry()` kann man sich ab Python 2.5 sparen wenn man `collections.defaultdict()` verwendet. Davor geht es mit der `dict.setdefault()`-Methode einfacher als mit dem ``if``/``else``.

Das entfernen des Zeilenendes geht mit der `rstrip()`-Methode etwas eleganter.
Benutzeravatar
akis.kapo
User
Beiträge: 127
Registriert: Freitag 1. September 2006, 12:58

Beitragvon akis.kapo » Donnerstag 17. Januar 2008, 15:11

Ok ich habs jetzt etwas umgestellt:

Code: Alles auswählen

#!/usr/bin/python

from __future__ import with_statement
import os
import md5
import sys

def fsize(fname):
    return os.path.getsize(fname) % 2**64

def fmd5sum(file):
    return md5.new(file.read()).digest()

def addentry(dict, pkey, fname):
    if dict.has_key(pkey):
        dict[pkey].add(fname)
    else:
        valset = set()
        dict[pkey] = valset.add(fname)

### main programm ###

mydict = dict()

for fname in sys.stdin.read().split('\0')[:-1]:
    with open(fname) as file:
        pkey = str(fsize(fname)) + fmd5sum(file)
        addentry(mydict, pkey, fname)

print mydict


Mein Problem ist jetzt aber, dass die Dateinamen garnicht hinzugefügt werden, als Wert für die jeweiligen Primary Keys.

Output vom Programm ist:

[code=]smitty@smitty-PC tmp $ find . -maxdepth 1 -type f -print0 | ./myfinddups.py
{'30463\xdc\xce\xe7"K\x83n\xf4\xba\xb1\xa1\x8b\xdem\x89': None, '166247792`M1\xdf\xf5o\xaf\xdbV\x90SH\x8c\xf0#': None, '2406400\x9e~\x0f\xa7\xde\xae!\xdbg\x00\x1d\xe1,"\x8f\xfc': None, '122880\xd1[DZ\x94\xe2\xbd\x1c^^\xce\xac\x8aU!\xda': None, '770S\xa36\xb9\x1f*\x94b\xce\xcf\xd6\xcd\x9b"\xe2Q': None, '752\xbf\x01\xd9\xee3\x13\xfd\xbc\xaa\xbe\x91\xc7\xa9\xcf0:': None, '583\xc7\x14\xfe\x0c<\x91\x1b\\%\x01a\x89n\x9f\x07\xb3': None, '14113#\xf3^C\x11D+\xf7}\xd3L6\x98b\x03\xb0': None}[/code]

Man kann direkt erkennen, dass er die Dateigrössen richtig einträgt als Schlüssel (+ md5 Summen als \xFOOBAR), aber anstelle der entsprechenden Dateinamen steht "None".

Ich sehe meinen Fehler nicht. Ich füge doch mit dict[pkey].add(fname) den Dateinamen ein, oder wieso landet er da nicht?

EDIT:

PS: Wie kann ich ein Python Integer (modulo 2 hoch 64) zu einem 64 bit String umwandeln?
Ich will eine fixe Grösse von 64bit oder 8 byte für die filesize haben, welche ich mit den fixen 128 bit des md5s concantenieren will (als pkey-192bit-string).
Momentan (siehe Code oben) habe ich ein dreckigen Workaround mit der Umwandlung in einen String (igit, igit). Da muss ich demnächst weg von.
BlackJack

Beitragvon BlackJack » Donnerstag 17. Januar 2008, 16:29

Bei dem Quelltext komme ich gar nicht bis zum Ende sondern es bricht schon hier ab:

Code: Alles auswählen

bj@s8n:~$ find txt -maxdepth 1 -type f -print0 | python forum.py
Traceback (most recent call last):
  File "forum.py", line 29, in <module>
    addentry(mydict, pkey, fname)
  File "forum.py", line 17, in addentry
    dict[pkey].add(fname)
AttributeError: 'NoneType' object has no attribute 'add'


Bei einer doppelten Datei passiert das. Überleg Dir mal genau was Deine Zeile 19 macht.

Für das Integer-Objekt als Bytestring schau Dir mal das `struct`-Modul an.
Benutzeravatar
akis.kapo
User
Beiträge: 127
Registriert: Freitag 1. September 2006, 12:58

Beitragvon akis.kapo » Donnerstag 17. Januar 2008, 18:11

Danke für den Hinweis @Zeile19.
Ich dachte:

Code: Alles auswählen

valset = set()
valset.add(foobar)
dict[pkey]=valset


sei äquivalent zu:

Code: Alles auswählen

valset = set()
dict[pkey]=valset.add(foobar)


Stimmt offensichtlich nciht und nur das erste Beispiel ist richtig.
Hab ich wohl an der falschen Stelle ne Zeile gespart.

Danke auch für den Hinweis @struct.

PS: Das Programm tut jetzt wie ichs eigentlich geplant hatte. :)
Benutzeravatar
akis.kapo
User
Beiträge: 127
Registriert: Freitag 1. September 2006, 12:58

Beitragvon akis.kapo » Donnerstag 17. Januar 2008, 22:38

Ok also seit 5 min sieht mein Programm so aus:

Code: Alles auswählen

#!/usr/bin/python

import os
import md5
import sys
from struct import *

def fsize(fname):
    return os.path.getsize(fname) % 2**64

def fmd5sum(file):
    return md5.new(file.read()).digest()

def addentry(dict, pkey, fname):
    if dict.has_key(pkey):
        dict[pkey].add(fname)
    else:
        valset = set()
        valset.add(fname)
        dict[pkey] = valset

### main programm ###

mydict = dict()

for fname in sys.stdin.read().split('\0')[:-1]:
    file = open(fname)
    pkey = pack('Q', fsize(fname)) + fmd5sum(file)
    addentry(mydict, pkey, fname)
    file.close()

print mydict


Ich denke die Verwendung von struct ist nun so, wie du es empfohlen hast. @blackjack

Danke für die Tipps.
Benutzeravatar
Leonidas
Administrator
Beiträge: 16023
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Beitragvon Leonidas » Donnerstag 17. Januar 2008, 23:53

Naja, jetzt kannst du dich mal daran machen das Programm in Funktionen zu verpacken, die Stern-Importe loszuwerden, die Funktionsnamen an PEP 8 anzupassen, aufhören Builtins zu überschrieben (dict, file), ``hashlib`` zu verwenden (spreche ich an, weil du scheinbar Python 2.5 mit dem ``with``-Statement nutzt). Achja, entweder nutzt du das, oder schließt die Dateien explizit, damit keine offenen Referenzen über bleiben. Statt ``has_key`` kannst du schon lange ``in`` verwenden. Das ``valset`` in zwei Schritten zu erzeugen leuchtet mir auch nicht sonderlich ein.
My god, it's full of CARs! | Leonidasvoice vs Modvoice
Benutzeravatar
akis.kapo
User
Beiträge: 127
Registriert: Freitag 1. September 2006, 12:58

Beitragvon akis.kapo » Freitag 18. Januar 2008, 02:04

Python Library Reference:
3.8 Mapping Types -- dict

k in a | True if a has a key k, else False
a.has_key(k) | Equivalent to k in a, use that form in new code


Laut oberer Aussage ist aber gerade has_key die neue Form für a in b.

Ansonsten müsste nach deinen Einwänden das Programm in Zukunft so aussehen:

Code: Alles auswählen

#!/usr/bin/python

import os
import hashlib
import sys
from struct import pack

def get_fsize(fname):
    return os.path.getsize(fname) % 2**64

def get_fdigest(infile):
    fdigest = hashlib.new(use_digest)
    fdigest.update(infile.read())
    return fdigest.digest()

def addentry(indict, pkey, fname):
    if use_abspath:
        fname = os.path.abspath(fname)
    if use_realpath:
        fname = os.path.realpath(fname)
    if indict.has_key(pkey):
        indict[pkey].add(fname)
    else:
        indict[pkey] = set([fname])

### main programm ###

mydict = dict()

# select digest algorithm
use_digest = 'md5' # default = 'md5'
#use_digest = 'ripemd160'
#use_digest = 'sha512'

# store absolute filenames. same as "readlink -f file" in unix.
use_abspath = False # default = False

# follow symbolic links first, then store the resulting filename.
use_realpath = False # default = False

for fname in sys.stdin.read().split('\0')[:-1]:
    file = open(fname)
    fsize = get_fsize(fname)
    if fsize > 0: # zerobyte files are booooring...
        addentry(mydict, pack('!Q', fsize) + get_fdigest(file), fname)
    file.close()

print mydict



Das with statement habe ich wieder weggemacht, um abwärtskompatibel zu bleiben (py <v2.5).

Müsste mir nur noch gescheite Kommandoparameter einfallen lassen, um den digest (use_digest) von aussen zu setzen und die Fileliste flexibler einlesen zu können (nimmt momentan nur \0 separierte strings von stdin).

Hey, was man nicht alles in wenigen Minuten hinbekommt in Python. :)

Hier mal 5 min, da mal 5 min coden. Immer zwischendrin Tipps ausm Forum lesen und gleich umsetzen. Cool.

Wenn das so weitergeht wird mein Prog vielleicht eleganter/kleiner/schneller/besser als all die anderen Finddup Klones.
Benutzeravatar
mkesper
User
Beiträge: 919
Registriert: Montag 20. November 2006, 15:48
Wohnort: formerly known as mkallas
Kontaktdaten:

Beitragvon mkesper » Freitag 18. Januar 2008, 08:45

Python Library Reference 2.5 hat geschrieben:3.8 Mapping Types -- dict

k in a | True if a has a key k, else False
a.has_key(k) | Equivalent to k in a, use that form in new code

*augenreib* Ich habe es gerade nochmal nachgeschaut, das verwundert mich jetzt auch...
BlackJack

Beitragvon BlackJack » Freitag 18. Januar 2008, 09:52

Das 'that form' betzieht sich in dem Satz auf das ``in`` nicht auf `has_key()`. In Python 3.0 wird's `dict.has_key()` wohl nicht mehr geben.

Code: Alles auswählen

Python 3.0a1 (py3k, Aug 31 2007, 21:20:42)
[GCC 4.1.2 (Ubuntu 4.1.2-0ubuntu4)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> dict.has_key
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'dict' has no attribute 'has_key'


Man könnte beides im vorliegenden Quelltext vermeiden, wenn man die `setdefault()`-Methode verwendet. Oder halt ein `collections.defaultdict` falls man nicht unbedingt auf Python <2.5 angewiesen ist.

Code: Alles auswählen

    indict.setdefault(pkey, set()).add(fname)


Ich würde noch empfehlen Konstanten wie z.B. `use_abspath`, komplett gross zu schreiben und an den Anfang des Quelltextes zu verschieben. Und das Hauptprogramm könnte man auch noch in eine Funktion stecken und mit

Code: Alles auswählen

if __name__ == '__main__':
    main()


vor der Ausführung schützen wenn man das Modul nicht ausführt, sondern importiert.

Ein kleiner Bug ist in der Abfrage von `fsize` auf 0: Es werden hier nicht nur leere Dateien ignoriert, sondern alle Dateien mit vielfachen Grössen von 2**64. Ich weiss, das klingt jetzt ein bisschen utopisch, aber es gab auch mal eine Zeit, da dachte man zwei Ziffern für Jahreszeitangaben seien genug, und die Programme werden um 2000 herum ganz bestimmt nicht mehr im Einsatz sein. ;-)
Benutzeravatar
Leonidas
Administrator
Beiträge: 16023
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Beitragvon Leonidas » Freitag 18. Januar 2008, 11:59

akis.kapo hat geschrieben:
Python Library Reference:
3.8 Mapping Types -- dict

k in a | True if a has a key k, else False
a.has_key(k) | Equivalent to k in a, use that form in new code


Laut oberer Aussage ist aber gerade has_key die neue Form für a in b.

Da ich dadurch sehr verwundert war und BlackJacks Aussage nicht gesehen habe, bin ich parallel dazu zum Schluss gekommen, dass das 'that' sich auf ``in`` bezieht. Also wird dazu geraten.

akis.kapo hat geschrieben:Das with statement habe ich wieder weggemacht, um abwärtskompatibel zu bleiben (py <v2.5).

Dann musst du die ``hashlib`` aber auch wieder rausnehmen. Und den Code auf Modul-Ebene kannst du in eine ``main()`` Funktion packen, die dann in einer ``if __name__ == '__main__':\n main()``-Abfrage gestartet wird. Dann kann man dein Programm auch gefahrlos importieren.
My god, it's full of CARs! | Leonidasvoice vs Modvoice
Benutzeravatar
akis.kapo
User
Beiträge: 127
Registriert: Freitag 1. September 2006, 12:58

Beitragvon akis.kapo » Freitag 18. Januar 2008, 13:32

[Leo von Lethal Weapon]
Ok ok ok ok!
[/Leo von Lethal Weapon]

Es ist erstaunlich, wie schnell und zielgerecht man in Python mit Hilfe von docs.python.org so wage Andeutungen zur Codeverbesserung (von BlackJack und co.) sicher umsetzen kann.

Habe noch nie was von collections gehört, ausser dass es das schon in java und c++ gab.

Aber nun gut. Alle Eindrücke aufgenommen und umgesetzt, hier bitteschön:

Code: Alles auswählen

#!/usr/bin/python

from __future__ import with_statement
from struct import pack
from collections import defaultdict
import os
import hashlib
import sys

# select digest algorithm
USE_DIGEST = 'md5' # default = 'md5'
#USE_DIGEST = 'ripemd160'
#USE_DIGEST = 'sha512'

# store absolute filenames. same as "readlink -f file" in unix.
USE_ABSPATH = False # default = False

# follow symbolic links first, then store the resulting filename.
USE_REALPATH = False # default = False

def get_fsize(fname):
    return os.path.getsize(fname) % 2**64

def get_fdigest(infile):
    fdigest = hashlib.new(USE_DIGEST)
    fdigest.update(infile.read())
    return fdigest.digest()

def addentry(indict, pkey, fname):
    if USE_ABSPATH:
        fname = os.path.abspath(fname)
    if USE_REALPATH:
        fname = os.path.realpath(fname)
    indict[pkey].add(fname)

def processfile(mydict, fname):
    with open(fname) as f:
        fsize = get_fsize(fname)
        if fsize > 0: # zerobyte files are booooring...
            addentry(mydict, pack('!Q', fsize) + get_fdigest(f), fname)

### main programm ###

def main():
    mydict = defaultdict(set)

    filelist = sys.stdin.read().split('\0')[:-1]

    for fname in filelist:
        processfile(mydict, fname)

    print mydict

if __name__ == "__main__":
    main()



Final ists immer noch nicht, aber immerhin ein ganzes Stückchen "wiederverwendbarer".

Übrigends setz ich nun doch auf python >= v2.5. (Wenn schon, denn schon).

Danke euch.

Und hört ja nicht auf am Code rumzumeckern! Verbesserungsvorschläge sind erwünscht.
Benutzeravatar
akis.kapo
User
Beiträge: 127
Registriert: Freitag 1. September 2006, 12:58

Beitragvon akis.kapo » Samstag 26. Januar 2008, 00:43

Code: Alles auswählen

#!/usr/bin/python

from __future__ import with_statement
from struct import pack
from collections import defaultdict
import os
import hashlib
import sys

# select digest algorithm
USE_DIGEST = 'md5' # default = 'md5'
#USE_DIGEST = 'ripemd160'
#USE_DIGEST = 'sha512'

# store absolute filenames. same as "readlink -f file" in unix.
USE_ABSPATH = False # default = False

# follow symbolic links first, then store the resulting filename.
USE_REALPATH = False # default = False

def get_fdigest(infile):
    fdigest = hashlib.new(USE_DIGEST)
    fdigest.update(infile.read())
    return fdigest.digest()

def addentry(indict, pkey, fname):
    if USE_ABSPATH:
        fname = os.path.abspath(fname)
    if USE_REALPATH:
        fname = os.path.realpath(fname)
    indict[pkey].add(unicode(fname))

def processfile(mydict, fname):
    with open(fname) as f:
        fsize = os.path.getsize(fname)
        fdigest = get_fdigest(f)
    noQs = get_noQs(fsize)
    pack_args = [(fsize >> (i*64)) % 2**64 for i in xrange(noQs)]
    if fsize > 0: # zerobyte files are booooring...
        addentry(mydict, pack(noQs*'!Q', *pack_args) + fdigest, fname)

def get_noQs(fsize):
    noQs = 1
    while max(fsize-1,1) >> (noQs*64):
        noQs += 1
    return noQs

### main programm ###

def main():
    mydict = defaultdict(set)

    filelist = sys.stdin.read().split('\0')[:-1]

    for fname in filelist:
        processfile(mydict, fname)

    print mydict

if __name__ == "__main__":
    main()



Jetzt mit beliebig grossen Dateien und Dateinamen werden (zukunft-)sicherheitshalber gleich in Unicode gespeichert.
BlackJack

Beitragvon BlackJack » Samstag 26. Januar 2008, 12:39

Das mit dem Unicode funktioniert nicht. Probier's mal mit Dateinamen aus, die Zeichen ausserhalb von ASCII enthalten.

Da unter Unix Dateinamen nur aus Bytes bestehen und durchaus Dateinamen mit verschiedenen Kodierungen nebeneinander existieren können, ist eher sicherer die Dateinamen *nicht* als Unicode zu speichern.

Und diesen "Stunt" mit dem umwandeln der (grossen) Zahlen habe ich auch nicht ganz verstanden. Was soll das!?
Benutzeravatar
akis.kapo
User
Beiträge: 127
Registriert: Freitag 1. September 2006, 12:58

Beitragvon akis.kapo » Samstag 26. Januar 2008, 16:26

BlackJack hat geschrieben:Und diesen "Stunt" mit dem umwandeln der (grossen) Zahlen habe ich auch nicht ganz verstanden. Was soll das!?


Eigentlich war mir der MD alleine nicht genug als "Fingerabdruck".
Ich wollte unbedingt die Dateigrösse in Bytes mit rein nehmen in den MD.
Der MD ist bei mir die Dateigrösse in x*64 bit Strings + MD5 String,
welches zugleich als Schlüssel für das Dictionary dient.

Ich wollte nur beliebig grosse Dateien, bzw. deren Dateigrösse, in
ein Byteformat umwandeln, mehr nicht. Einfach als vielfaches von 8 Byte,
je nach Bedarf mal mehr, mal weniger 8 Byte Päckchen.

Gehts denn noch einfacher?

EDIT:
Aber jetzt bitte nichts mit Pickle antworten...

Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder