文件的类包装器——何时正确关闭文件句柄
我有一个类,用来处理一些文件操作的功能。另一个类会创建这个 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 个回答
你写的这个类并不能保证文件会更可靠地关闭。如果你只是把文件处理器的实例“扔掉”,那么文件就不会关闭,直到这个对象被销毁。这个销毁可能会很快,也可能要等到这个对象被垃圾回收时才会发生,但单纯把一个文件对象“扔掉”也会一样快地关闭它。如果对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)
这样就能确保文件会及时关闭。
__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