2011-10-19 18 views
16

Con python 2.7 il codice seguente calcola l'esadecimale mD5 del contenuto di un file.Utilizzo di hashlib per calcolare il digest md5 di un file in Python 3

(MODIFICA: beh, non proprio come hanno dimostrato le risposte, l'ho solo pensato).

import hashlib 

def md5sum(filename): 
    f = open(filename, mode='rb') 
    d = hashlib.md5() 
    for buf in f.read(128): 
     d.update(buf) 
    return d.hexdigest() 

Ora, se corro che il codice usando python3 si solleva un'eccezione TypeError:

d.update(buf) 
TypeError: object supporting the buffer API required 

ho capito che avrei potuto fare che eseguire codice con entrambe python2 e python3 cambiarlo con:

def md5sum(filename): 
    f = open(filename, mode='r') 
    d = hashlib.md5() 
    for buf in f.read(128): 
     d.update(buf.encode()) 
    return d.hexdigest() 

Ora mi chiedo ancora perché il codice originale ha smesso di funzionare. Sembra che aprendo un file usando il modificatore della modalità binaria restituisca interi invece di stringhe codificate come byte (lo dico perché type (buf) restituisce int). Questo comportamento è spiegato da qualche parte?

+1

correlato: http://stackoverflow.com/q/4949162/ – jfs

+1

Sarebbe più veloce se avete fatto grande legge, più vicino alla dimensione del blocco del file del file system? (Per esempio 1024 byte su ext3 Linux e 4096 byte o più di Windows NTFS) – rakslice

risposta

24

Penso che si voleva il ciclo for per effettuare chiamate successive alla f.read(128). Ciò può essere fatto utilizzando iter() e functools.partial():

import hashlib 
from functools import partial 

def md5sum(filename): 
    with open(filename, mode='rb') as f: 
     d = hashlib.md5() 
     for buf in iter(partial(f.read, 128), b''): 
      d.update(buf) 
    return d.hexdigest() 

print(md5sum('utils.py')) 
+0

Sì, è esattamente quello che stavo cercando di fare. Alla fine l'ho raggiunto con una soluzione meno elegante della tua usando un generatore. – kriss

+0

Questo perde l'handle del file su alcune implementazioni Python. Dovresti almeno chiamare "chiudi". – phihag

+1

Ho aggiunto l'istruzione 'with' per chiudere correttamente il file. – jfs

10
for buf in f.read(128): 
    d.update(buf) 

.. aggiorna l'hash sequenza con ciascuno dei primi 128 byte valori del file. Poiché l'iterazione su uno bytes produce oggetti int, si ottengono le seguenti chiamate che causano l'errore che si è verificato in Python3.

d.update(97) 
d.update(98) 
d.update(99) 
d.update(100) 

che non è quello che vuoi.

Invece, si vuole:

def md5sum(filename): 
    with open(filename, mode='rb') as f: 
    d = hashlib.md5() 
    while True: 
     buf = f.read(4096) # 128 is smaller than the typical filesystem block 
     if not buf: 
     break 
     d.update(buf) 
    return d.hexdigest() 
+1

Si tratta di mangiare tutta la RAM se si apre un file enorme. Ecco perché tamponiamo. –

+0

@fastreload Già aggiunto quello;). Poiché la soluzione originale non funzionava nemmeno con file con 128 byte, non penso che la memoria sia un problema, ma ho aggiunto comunque una lettura bufferizzata. – phihag

+0

Ben fatto, ma OP ha sostenuto che poteva usare il suo codice in Python 2.x e ha smesso di funzionare su 3.x. E ricordo di aver creato un buffer da 1 byte per il calcolo di md5 di file iso da 3 GB per il benchmarking e non ha avuto esito negativo. La mia scommessa è che Python 2.7 ha un meccanismo sicuro che qualunque sia l'input dell'utente, la dimensione minima del buffer non scende al di sotto di un certo livello. Che ne dici? –

1

fine ho cambiato il mio codice per la versione di sotto (che trovo facile da capire) dopo aver chiesto la questione. Ma probabilmente lo cambierò nella versione suggerita da Raymond Hetting unsing functools.partial.

import hashlib 

def chunks(filename, chunksize): 
    f = open(filename, mode='rb') 
    buf = "Let's go" 
    while len(buf): 
     buf = f.read(chunksize) 
     yield buf 

def md5sum(filename): 
    d = hashlib.md5() 
    for buf in chunks(filename, 128): 
     d.update(buf) 
    return d.hexdigest() 
+0

Ora funziona se la lunghezza del file non è un multiplo di chunksize, read restituirà un buffer più breve nell'ultima lettura. La terminazione è data da un buffer vuoto, ecco perché la condizione "not buf" nel codice di esempio sopra (che funziona). – Mapio

+0

@Mapio: c'è effettivamente una specie di bug nel mio codice, ma non del tutto dove dici. La lunghezza del file è irrilevante. Il codice precedente funziona a condizione che non ci siano letture parziali che restituiscono buffer incompleti. Se si verifica una lettura parziale, si interromperà troppo presto (ma prendendo in considerazione il buffer parziale). In alcuni casi può verificarsi una lettura parziale, ad esempio se il programma riceve un segnale di interruzione gestito durante la lettura, quindi continua con la lettura dopo il ritorno dall'interruzione. – kriss

+0

Bene, nel commento sopra, quando si parla di "codice sopra" mi riferisco alla vecchia versione. Questo attuale sta funzionando (anche se non è la soluzione migliore possibile). – kriss