可以依赖引用计数来关闭Python中的文件吗?

时间:2016-12-29 08:21:28

标签: python file garbage-collection reference-counting resource-leak

在这个问题“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灵活性并使用文件名参数并使用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模块的文档中可以看出,引用计数是该语言的核心功能。

而且,如果我确实做错了什么,你会怎么建议我重新编写它以保持列表理解的简洁清晰度和适用于任何可以像文件一样读取的任何东西的界面的灵活性?

1 个答案:

答案 0 :(得分:0)

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

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

问题是如何处理它。问题中的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作为上下文管理器,但我觉得这种方式会以微妙的方式打破期望。它使noResolve关闭文件,它的名称类似于承诺,它将计算哈希值,而不是关闭文件。

我怀疑在你有字节块的情况下有很多情况,并希望将它们全部哈希,就像它们是连续块或它们的流的一部分一样。在字节块上散列迭代器甚至更加通用,然后散列文件。

同样地,我认为在很多情况下,您希望迭代文件类对象然后关闭它。这使得这两个功能都可以重复使用和通用。