Python 中的文件是否可以依靠引用计数来关闭?

Can reference counting be relied on to close a file in Python?

在这个问题“Generating an MD5 checksum of a file”中,我有这样的代码:

import hashlib
def hashfile(afile, hasher, blocksize=65536):
    buf = afile.read(blocksize)
    while len(buf) > 0:
        hasher.update(buf)
        buf = afile.read(blocksize)
    return hasher.digest()

[(fname, hashfile(open(fname, 'rb'), hashlib.sha256())) for fname in fnamelst]

我因在列表理解内打开文件而受到批评,有人认为如果我有足够长的列表,我会 运行 打开文件句柄。建议使用显着降低 hashfile 灵活性并使 hashfile 采用文件名参数并使用 with 的接口。

这些有必要吗?我真的做错了什么吗?

正在测试这段代码:

#!/usr/bin/python3

import sys
from pprint import pprint # Pretty printing

class HereAndGone(object):
    def __init__(self, i):
        print("%d %x -> coming into existence." % (i, id(self)),
              file=sys.stderr)
        self.i_ = i
    def __del__(self):
        print("%d %x <- going away now." % (self.i_, id(self)),
              file=sys.stderr)

def do_nothing(hag):
    return id(hag)

l = [(i, do_nothing(HereAndGone(i))) for i in range(0, 10)]

pprint(l)

此输出结果:

0 7f0346decef0 -> coming into existence.
0 7f0346decef0 <- going away now.
1 7f0346decef0 -> coming into existence.
1 7f0346decef0 <- going away now.
2 7f0346decef0 -> coming into existence.
2 7f0346decef0 <- going away now.
3 7f0346decef0 -> coming into existence.
3 7f0346decef0 <- going away now.
4 7f0346decef0 -> coming into existence.
4 7f0346decef0 <- going away now.
5 7f0346decef0 -> coming into existence.
5 7f0346decef0 <- going away now.
6 7f0346decef0 -> coming into existence.
6 7f0346decef0 <- going away now.
7 7f0346decef0 -> coming into existence.
7 7f0346decef0 <- going away now.
8 7f0346decef0 -> coming into existence.
8 7f0346decef0 <- going away now.
9 7f0346decef0 -> coming into existence.
9 7f0346decef0 <- going away now.
[(0, 139652050636528),
 (1, 139652050636528),
 (2, 139652050636528),
 (3, 139652050636528),
 (4, 139652050636528),
 (5, 139652050636528),
 (6, 139652050636528),
 (7, 139652050636528),
 (8, 139652050636528),
 (9, 139652050636528)]

很明显,每个 HereAndGone 对象都在构造列表理解的每个元素时被创建和销毁。 Python 一旦没有对对象的引用,引用计数就会释放该对象,这会在计算该列表元素的值后立即发生。

当然,也许其他一些 Python 实现不会这样做。 Python 实现是否需要进行某种形式的引用计数?从 gc 模块的文档看来,引用计数确实是该语言的核心功能。

而且,如果我确实做错了什么,您会建议我如何重写它以保持列表理解的简洁明了以及界面的灵活性,该界面可以处理任何可以像文件一样读取的内容?

有人指出 Data Model section of the Python Language Reference 非常明确地说“当对象变得不可访问时,不要依赖于对象的立即终结(因此您应该始终明确地关闭文件)。”。因此,这清楚地表明代码依赖于无法保证的行为。

即使是,它仍然很脆弱。它无形地依赖于文件永远不会被具有循环引用或生命周期超出单个文件散列的数据结构引用。谁知道将来代码会发生什么,是否有人会记住这个关键细节?

问题是如何处理它。问题中的 hashfile 函数非常灵活,fiddle 它的接口采用文件名并让它在函数内部打开文件,从而扼杀了它的灵活性,这似乎是一种耻辱。我认为最小的解决方案是:

我觉得解决办法是稍微重新考虑界面并使其更加通用。

def hash_bytestr_iter(hasher, bytesiter, ashexstr=False):
    for block in bytesiter:
        hasher.update(bytesiter)
    return (hasher.hexdigest() if ashexstr else hasher.digest())

def iter_and_close_file(afile, blocksize=65536):
    with afile:
        block = afile.read(blocksize)
        while len(block) > 0:
            yield block

可以让原始的 hashfile 使用传入的 afile 作为上下文管理器,但我觉得这种方式以微妙的方式打破了预期。它使 hashfile 关闭文件,它的名字有点像承诺它会计算哈希值,而不是关闭文件。

而且我怀疑在很多情况下,当您有字节块并且想要将它们全部散列时,就好像它们是连续块或它们的流的一部分一样。在字节块上散列迭代器比散列文件更通用。

同样,我认为在很多情况下,您希望遍历一个类似文件的对象,然后将其关闭。所以这使得这两个功能都可以重用和通用。