2011-10-19 27 views
16

Con python 2.7 el siguiente código calcula el hexdigest mD5 del contenido de un archivo.Usando hashlib para calcular md5 resumen de un archivo en Python 3

(EDITAR: bueno, realmente no como las respuestas han demostrado, eso pensé).

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() 

Ahora si me quedo ese código usando python3 que provocará una excepción TypeError:

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

me di cuenta de que podía hacer que el código de ejecución tanto con python2 y python3 cambiándolo a:

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

Ahora todavía me pregunto por qué el código original dejó de funcionar. Parece que al abrir un archivo utilizando el modificador de modo binario, devuelve números enteros en lugar de cadenas codificadas como bytes (lo digo porque el tipo (buf) devuelve int). ¿Este comportamiento se explica en alguna parte?

+1

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

+1

¿Sería más rápido si se hizo más grande lee, más cercano al tamaño de bloque de archivos del sistema de archivos? (por ejemplo, 1024 bytes en Linux ext3 y 4096 bytes o más en Windows NTFS) – rakslice

Respuesta

24

Creo que quería que el ciclo for para realizar llamadas sucesivas a f.read(128). Eso se puede hacer usando iter() y 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í, eso es exactamente lo que estaba tratando de hacer. Finalmente lo logré con una solución menos elegante que la tuya usando un generador. – kriss

+0

Esto filtra el identificador de archivo en algunas implementaciones de Python. Al menos debería llamar 'cerrar'. – phihag

+1

He agregado la instrucción 'with' para cerrar el archivo correctamente. – jfs

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

.. actualiza los datos de forma secuencial hash con cada uno de los primeros 128 bytes valores del archivo. Dado que iterar sobre un bytes produce objetos int, recibe las siguientes llamadas que causan el error que encontró en Python3.

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

que no es lo que quiere.

su lugar, desea:

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

Se come toda la RAM si abre un archivo enorme. Es por eso que amortizamos. –

+0

@fastreload Ya agregué eso;). Como la solución original ni siquiera funcionaba para archivos con> 128 bytes, no creo que la memoria sea un problema, pero de todos modos agregué una lectura en búfer. – phihag

+0

Bien hecho, pero OP afirmó que podría usar su código en Python 2.x y dejó de trabajar en 3.x. Y recuerdo que hice un buffer de 1 byte para calcular md5 de 3 gb de archivo iso para benchmarking y no falló. Mi apuesta es que Python 2.7 tiene un mecanismo a prueba de fallas que independientemente de la entrada del usuario, el tamaño mínimo de la memoria intermedia no baja de un cierto nivel. ¿Qué dices? –

1

finalmente he cambiado de código para la versión más abajo (que me resulta fácil de entender), después de hacer la pregunta. Pero probablemente lo cambie a la versión sugerida por Raymond Hetting 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

Esto ahora funcionará si la longitud del archivo no es un múltiplo de chunksize, read devolverá de hecho un buffer más corto en la última lectura. La terminación está dada por un búfer vacío, por eso la condición "no buf" en el código de ejemplo anterior (que funciona). – Mapio

+0

@Mapio: de hecho, hay un tipo de error en mi código, pero no en absoluto donde dices. La longitud del archivo es irrelevante. El código anterior funciona siempre que no haya lectura parcial devolviendo almacenamientos intermedios incompletos. Si se produce una lectura parcial, se detendrá demasiado pronto (pero teniendo en cuenta el buffer parcial). En algunos casos, puede producirse una lectura parcial, por ejemplo, si el programa recibe una señal de interrupción administrada durante la lectura, luego continúe con la lectura después de regresar de la interrupción. – kriss

+0

Bueno, en el comentario anterior, cuando hablamos del "código anterior" me refiero a la versión anterior. Este actual está funcionando (incluso si no es la mejor solución posible). – kriss

Cuestiones relacionadas