【问题标题】:Using hashlib to compute md5 digest of a file in Python 3在 Python 3 中使用 hashlib 计算文件的 md5 摘要
【发布时间】:2011-12-11 09:14:13
【问题描述】:

在 python 2.7 中,以下代码计算文件内容的 mD5 hexdigest。

(编辑:嗯,不是真的如答案所示,我只是这么认为)。

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)。这种行为是否在某处解释过?

【问题讨论】:

  • 如果你进行更大的读取会更快,更接近文件系统的文件块大小吗? (例如,Linux ext3 上为 1024 字节,Windows NTFS 上为 4096 字节或更多)

标签: python python-3.x hashlib


【解决方案1】:

我认为您希望 for 循环连续调用 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'))

【讨论】:

  • 是的,这正是我想要做的。我终于用比你使用生成器的解决方案更优雅的解决方案实现了这一点。
  • 这会泄露一些 Python 实现的文件句柄。你至少应该打电话给close
  • 我已添加with 语句以正确关闭文件。
  • @phihag:真的有python实现自动关闭实际上leaks文件处理吗?我认为它只是将这些文件句柄的释放延迟到垃圾收集?
  • @RaymondHettinger:如果你不喜欢它;只需还原更改。我认为这是一个太小的变化,无法讨论。虽然我非常不同意你的推理。公共代码应该遵循最佳实践尤其是如果它是针对初学者的。如果对于这样一个常见的任务,最佳实践太难遵循(尽管我不认为是这样),那么语言应该改变。
【解决方案2】:
for buf in f.read(128):
  d.update(buf)

.. 使用文件的前 128 个 bytes 值中的每一个顺序更新哈希。由于迭代 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()

【讨论】:

  • 如果你打开一个大文件,这会吃掉整个 RAM。这就是我们缓冲的原因。
  • @fastreload 已经添加了 ;)。由于原始解决方案甚至不适用于大于 128 字节的文件,因此我认为内存不是问题,但我还是添加了缓冲读取。
  • 干得好,但 OP 声称他可以在 Python 2.x 中使用他的代码并停止在 3.x 上工作。我记得我制作了 1 个字节的缓冲区来计算 3 gb iso 文件的 md5 以进行基准测试,它没有失败。我敢打赌,python 2.7 有一个故障保护机制,无论用户输入是什么,最小缓冲区大小都不会低于某个水平。你说什么?
  • @fastreload 代码在 Python 2 中没有崩溃,因为迭代 str 产生了 str。对于大于 128 字节的文件,结果仍然是错误的。当然,您可以根据需要调整缓冲区大小(除非您有一个快速的 SSD,否则 CPU 无论如何都会感到无聊,并且好的操作系统会预加载文件的下一个字节)。 Python 2.7 绝对没有这种故障保护机制;这将违反read 的合同。 OP 只是没有将脚本的结果与规范的 md5sum 进行比较,或者没有将脚本的结果与两个具有 128 个相同首字节的文件的结果进行比较。
  • 是的,我的原始代码确实被破坏了(但还没有在野外)。我只是没有在相同开头的大文件上测试它。我应该猜到有一个真正的问题,因为它运行得太快了。
【解决方案3】:

在提出问题后,我终于将代码更改为以下版本(我觉得很容易理解)。但我可能会将其更改为 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()

【讨论】:

  • 如果文件长度不是块大小的倍数,这将起作用,读取实际上会在最后一次读取时返回一个较短的缓冲区。终止由一个空缓冲区给出,这就是上面示例代码中的“not buf”条件(有效)的原因。
  • @Mapio:我的代码中确实存在一种错误,但在您所说的地方根本没有。文件长度无关紧要。如果没有部分读取返回不完整的缓冲区,则上面的代码可以工作。如果发生部分读取,它将过早停止(但要考虑部分缓冲区)。在某些情况下可能会发生部分读取,例如如果程序在读取时收到托管中断信号,则在中断返回后继续读取。
  • 好吧,在上面的评论中,当谈到“上面的代码”时,我指的是旧版本。当前的这个现在正在工作(即使它不是最好的解决方案)。
猜你喜欢
  • 1970-01-01
  • 2013-05-18
  • 1970-01-01
  • 2015-01-26
  • 1970-01-01
  • 1970-01-01
  • 2013-07-17
  • 2010-12-13
  • 1970-01-01
相关资源
最近更新 更多