文件的类包装器——何时正确关闭文件句柄

15 投票
2 回答
6400 浏览
提问于 2025-04-17 08:44

我有一个类,用来处理一些文件操作的功能。另一个类会创建这个 filehandler 的实例,并且会用它一段不确定的时间。最终,当这个 caller 被销毁时,它也会把唯一指向 filehandler 的引用给销毁掉。

那么,最好的办法是什么,让 filehandler 关闭文件呢?

我现在使用的是 __del__(self),但是在看到一些不同的问题和文章后,我觉得这样做被认为是不太好的做法

class fileHandler:
    def __init__(self, dbf):
        self.logger = logging.getLogger('fileHandler')
        self.thefile = open(dbf, 'rb')
    def __del__(self):
        self.thefile.close()

这是处理器相关的部分。这个类的主要目的是简化与底层文件对象的操作细节,同时也避免不必要地将整个文件读入内存。不过,处理底层文件的一部分就是在对象超出作用域时关闭它。

这个 caller 不应该知道或关心 filehandler 的具体细节。关闭文件的工作应该由 filehandler 来完成,当它超出作用域时释放任何必要的资源。这也是最初设计它的原因之一。所以,我现在面临的选择是把 filehandler 的代码放到调用对象里,或者处理一个不够干净的抽象。

你有什么想法吗?

2 个回答

8

你写的这个类并不能保证文件会更可靠地关闭。如果你只是把文件处理器的实例“扔掉”,那么文件就不会关闭,直到这个对象被销毁。这个销毁可能会很快,也可能要等到这个对象被垃圾回收时才会发生,但单纯把一个文件对象“扔掉”也会一样快地关闭它。如果对thefile的唯一引用是在你的类对象内部,那么当filehandler被垃圾回收时,thefile也会被同时回收,从而关闭。

正确使用文件的方法是使用with语句:

with open(dbf, 'rb') as thefile:
    do_something_with(thefile)

这样可以确保每当with语句结束时,thefile都会被关闭。如果你想把文件放在另一个对象里面,也可以通过定义__enter____exit__方法来实现:

class FileHandler:
    def __init__(self, dbf):
        self.logger = logging.getLogger('fileHandler')
        self.thefile = open(dbf, 'rb')
    def __enter__(self):
        return self
    def __exit__(self, exc_type, exc_value, traceback):
        self.thefile.close()

然后你可以这样做:

with FileHandler(dbf) as fh:
    do_something_with(fh)

这样就能确保文件会及时关闭。

25

__del__ 本身并不是坏事。你只需要特别小心,不要在定义了 __del__ 的对象中创建循环引用。如果你真的需要创建循环引用(比如父对象引用子对象,子对象又引用回父对象),那么你应该使用 weakref 模块。

所以,__del__ 是可以的,只要注意循环引用的问题。

垃圾回收:这里重要的一点是,当一个对象超出作用范围时,它 可以 被垃圾回收,实际上,它 被垃圾回收……但是什么时候呢?没有具体的时间保证,不同的 Python 实现对此的表现也不同。因此,在管理资源时,最好明确地调用 .close() 方法来关闭你的 filehandler,或者如果你的使用方式合适,可以添加 __enter____exit__ 方法。

__enter____exit__ 方法 在这里有详细描述。它们一个很好的地方是,即使发生异常,__exit__ 也会被调用,这样你可以确保资源能够被正常关闭。

你的代码,增强了 __enter__/__exit__ 的使用:

class fileHandler:
    def __init__(self, dbf):
        self.logger = logging.getLogger('fileHandler')
        self.thefilename = dbf
    def __enter__(self):
        self.thefile = open(self.thefilename, 'rb')
        return self
    def __exit__(self, *args):
        self.thefile.close()

注意,文件是在 __enter__ 中打开的,而不是在 __init__ 中打开——这样可以让你只创建一次文件处理对象,然后在 with 语句中随时使用,而不需要每次都重新创建:

fh = filehandler('some_dbf')
with fh:
    #file is now opened
    #do some stuff
#file is now closed
#blah blah
#need the file again, so
with fh:
    # file is open again, do some stuff with it
#etc, etc 

撰写回答