__del__中以Pythonic方式关闭连接类对象
我正在做一个类似连接的对象,它实现了上下文管理器。像这样写是非常推荐的:
with MyConnection() as con:
# do stuff
当然,也可以这样做:
con = MyConnection()
# do stuff
con.close()
但是,如果不关闭连接,那就会出现问题。所以在__del__()
里关闭连接似乎是个好主意:
def __del__(self):
self.close()
这样看起来不错,但有时候会导致错误:
Exception ignored in: [...]
Traceback (most recent call last):
File "...", line xxx, in __del__()
TypeError: 'NoneType' object is not callable
看起来有时候在调用__del__()
时,关闭方法已经被销毁了。
所以我在寻找一种好的方法,来确保在对象被销毁时,Python能正确地关闭连接。如果可以的话,我希望在close()
和__del__()
中避免代码重复。
6 个回答
weakref.finalize()
这个功能可以让你在一个对象被垃圾回收的时候,或者程序退出时,执行一些操作。从 Python 3.4 开始就可以使用这个功能。
当你创建一个对象时,可以调用 finalize()
,并给它传入一个回调函数,这个函数可以用来清理你对象所占用的资源。
Python 的文档中提供了几个使用的例子:
>>> import weakref
>>> class Object:
... pass
...
>>> kenny = Object()
>>> weakref.finalize(kenny, print, "You killed Kenny!")
<finalize object at ...; for 'Object' at ...>
>>> del kenny
You killed Kenny!
还有这个例子,展示了一个表示临时目录的类,当以下情况发生时,它的内容会被删除:
- 调用它的
remove()
方法 - 被垃圾回收
- 程序退出
无论哪种情况先发生。
class TempDir:
def __init__(self):
self.name = tempfile.mkdtemp()
self._finalizer = weakref.finalize(self, shutil.rmtree, self.name)
def remove(self):
self._finalizer()
@property
def removed(self):
return not self._finalizer.alive
确实,你无法强迫用户使用好的编程技巧,如果他们不愿意这样做,你也不能为他们负责。
关于__del__
这个方法什么时候会被调用,没有固定的时间保证——在某些Python版本中,它会立刻被调用,而在其他版本中,可能要等到解释器关闭时才会调用。所以,虽然这不是一个很好的选择,但使用atexit这个模块可能是可行的——只要确保你注册的函数足够聪明,能够检查资源是否已经被关闭或销毁。
你可以试着在 __del__
里面调用 close
,然后忽略任何可能出现的错误:
del __del__(self):
try:
self.close()
except TypeError:
pass
没有办法保证 __del__
什么时候会真正执行。因为你在使用 with
语句,所以应该用 __exit__
方法。__exit__
会在 with
语句结束后立刻被调用,不管这个语句是正常结束的,还是因为出现了错误等情况。
如果你真的想防止用户不关闭连接,可以只在 __enter__
这个地方初始化连接,或者你可以加一个标志,来检查连接是否已经被上下文管理器初始化过。比如,可以这样做:
class MyConnection(object):
safely_initialized = False
def __enter__(self):
# Init your connection
self.safely_initialized = True
return self
def do_something(self):
if not self.safely_initialized:
raise Exception('You must initialize the connection with a context manager!')
# Do something
def __exit__(self, type, value, traceback):
# Close your connection
这样的话,连接就不会被初始化,除非是在上下文管理器里面。