【问题标题】:Can reference counting be relied on to close a file in Python?可以依靠引用计数来关闭 Python 中的文件吗?
【发布时间】:2017-05-13 13:02:24
【问题描述】:

在这个问题“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 模块的文档看来,引用计数确实是该语言的核心特性。

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

【问题讨论】:

  • “Python 实现是否需要进行某种形式的引用计数?” - 没有。
  • “从 gc 模块的文档看来,引用计数确实是该语言的核心特性。” - 大多数 gc 模块,尤其是关于关闭它的部分,应该被视为可选功能。
  • 修改hashfile,使其采用文件名并处理文件本身的打开和关闭。一般而言,使用系统的内存管理来管理其他资源是一个糟糕的主意。
  • @tfb - 这看起来非常丑陋。为什么要把一个非常好的通用功能变成一个非常特定用途的功能,只是为了控制资源的范围?
  • 引用语言参考的“数据模型”部分:当对象变得不可访问时,不要依赖于立即完成(因此您应该始终明确关闭文件)跨度>

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


【解决方案1】:

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

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

问题是如何处理它。问题中的hashfile 函数非常灵活,摆弄它的接口以获取文件名并让它在函数内打开文件并因此扼杀它的灵活性似乎是一种耻辱。我认为最小的解决方案是:

我认为解决方案是重新考虑界面并使其更加通用。

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 关闭文件,它的名字有点承诺它会计算一个哈希值,而不是关闭文件。

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-05-06
    • 2011-09-03
    • 1970-01-01
    • 2012-01-10
    • 2010-11-26
    • 1970-01-01
    • 2016-01-10
    相关资源
    最近更新 更多