Использование hashlib для вычисления md5 digest файла в Python 3
С python 2.7 следующий код вычисляет mD5 hexdigest содержимого файла.
(EDIT: ну, не так, как показали ответы, я так и думал).
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()
Теперь, если я запустил этот код с помощью python3, он вызывает исключение TypeError:
d.update(buf)
TypeError: object supporting the buffer API required
Я понял, что могу сделать этот код запустившимся с python2 и python3, изменив его на:
def md5sum(filename):
f = open(filename, mode='r')
d = hashlib.md5()
for buf in f.read(128):
d.update(buf.encode())
return d.hexdigest()
Теперь я все еще удивляюсь, почему исходный код перестает работать. Кажется, что при открытии файла с использованием модификатора двоичного режима он возвращает целые числа вместо строк, закодированных как байты (я говорю, что, поскольку type (buf) возвращает int). Является ли это поведение объясненным где-то?
Ответы
Ответ 1
Я думаю, вы хотели, чтобы for-loop выполнял последовательные вызовы f.read(128)
. Это можно сделать с помощью iter() и 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'))
Ответ 2
for buf in f.read(128):
d.update(buf)
.. обновляет хэш последовательно с каждым из первых 128 байтов значений файла. Поскольку итерация по bytes
создает объекты int
, вы получаете следующие вызовы, которые вызывают ошибку, с которой вы столкнулись в Python3.
d.update(97)
d.update(98)
d.update(99)
d.update(100)
который не то, что вы хотите.
Вместо этого вы хотите:
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()
Ответ 3
Я, наконец, изменил свой код на версию ниже (которую я нахожу легко понятой) после того, как задал вопрос. Но я, вероятно, поменяю его на версию, предложенную Раймондом Хеттингом, отличным от functools.partial.
import hashlib
def chunks(filename, chunksize):
f = open(filename, mode='rb')
buf = "Let 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()