包装文件的类--当不再引用文件句柄时正确关闭文件句柄的方法

15
我有一个类,封装了一些我需要的文件处理功能。另一个类创建了一个filehandler实例,并在不确定的时间内使用它。最终,caller被销毁,这也销毁了对filehandler的唯一引用。如何最好地让filehandler关闭文件?我目前使用__del__(self),但在看了several different questions and articles之后,我认为这被认为是a bad thing
class fileHandler:
    def __init__(self, dbf):
        self.logger = logging.getLogger('fileHandler')
        self.thefile = open(dbf, 'rb')
    def __del__(self):
        self.thefile.close()

这是处理程序的相关部分。整个类的目的是抽象出与底层文件对象的操作细节,并避免不必要地将整个文件读入内存中。然而,处理底层文件的一部分是在对象超出作用域时关闭它。
调用者不应该知道或关心filehandler中涉及的细节。当对象超出作用域时,filehandler的工作是释放任何必要的资源。这也是它首先被抽象化的原因之一。所以,我似乎面临着将filehandler代码移入调用对象,或处理一个有泄漏的抽象的问题。
你有什么想法?
2个回答

24

__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 

1
非常好,这更接近我想要的。引用循环是我检查的第一件事,我没有任何引用循环。但是有些文章说,如果叶对象具有del方法,则整个对象链将无法收集,而不考虑引用循环。 - Spencer Rathbun
@SpencerRathbun:我找到的所有文档都指出,只有在存在引用循环的情况下,这些项目才是不可收集的。 - Ethan Furman
虽然它不完全是我想要的,但它能胜任工作。我接受你的答案。 - Spencer Rathbun
1
@SpencerRathbun:我没有添加.closeopen__del__方法,因为这会使我的示例变得复杂(而且我认为在__enter__中打开文件是一个很酷的技巧😉),但你可以随时将它们添加回去,然后可以根据需要以合理的方式使用filehandler。不过,请记住,在使用__del__时无法保证时间。 - Ethan Furman
1
@NiekdeKlein:因为__exit__实际上需要另外三个参数(如果发生异常,则为异常信息,否则为三个None),但我不需要它们被命名,因为代码不会有任何不同的操作:可能性1)发生异常--结果->关闭文件;可能性2)未发生异常--结果->关闭文件。 - Ethan Furman
显示剩余2条评论

8
你写的类并不能更可靠地关闭文件。如果你只是简单地将文件处理实例抛弃,那么在对象被销毁之前,文件不会被关闭。这可能会立即发生,也可能会在对象被垃圾回收时才发生,但是,如果只是将普通的文件对象丢弃,则会同样快速地关闭它。如果thefile的唯一引用是在你的类对象内部,则当filehandler被垃圾回收时,thefile也将被垃圾回收,因此同时关闭。
正确使用文件的方法是使用with语句:
with open(dbf, 'rb') as thefile:
    do_something_with(thefile)

使用with语句可以确保thefile在退出with代码块时总是关闭。如果你想将文件包装在另一个对象内,也可以通过定义__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)

确保文件得到及时关闭。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接