使用try-finally装饰器清理Python对象
在我编程的过程中,发现对象清理是一个很常见的问题。到目前为止,我一直按照推荐的方法使用 with
语句,具体可以参考这里
今天我有了另一个想法,我觉得这个方法更优雅(因为它不需要最终用户使用 with 语句)。这个想法是为某种类型的对象使用一个 try-finally 装饰器(也就是有一个清理方法)。
我只是想知道这种做法是否有问题,或者有没有更好的方法。我不喜欢我的很多类都需要用 with 语句来初始化,但我也想确保我的对象能够正确关闭。下面是一个简单的例子。
def cleanme(func):
def _decorator(self, *args, **kwargs):
try:
func(self, *args, **kwargs)
finally:
self._cleanup()
return _decorator
class IObject(object):
def __init__(self):
self.file_name = "some_file.txt"
self._file_object = None
self._cleaned = True
@cleanme
def run(self):
self._connect()
while True:
# do some things over a long period
pass
def _connect(self):
self._file_object = open(self.file_name)
self._cleaned = False
def _cleanup(self):
if not self._cleaned:
self._file_object.close()
self._cleaned = True
2 个回答
我觉得这种做法没问题,只要你的客户端只用你的 IOObject
来执行 run
方法,而不想直接调用 connect
,然后再用打开的 file_object
做其他操作。
上下文管理器让客户端很清楚地知道自己在哪里获取和清理资源,同时也给他们灵活性,可以随意使用这些资源,因为一旦他们离开 with
这个代码块,资源就会被自动清理。用这种方法就不太清楚了;客户端需要查看代码(或者文档)才能知道 run
方法会为他们清理资源,但如果直接使用 connect
,就需要手动调用 cleanup
来正确清理。
还有一个Python的原则叫做“显式优于隐式”。with
语句明确地告诉客户端在哪里获取和释放资源。用装饰器的方法就失去了这一点。
让我来指出几个问题。
首先,你要求你的类里必须有一个 cleanup()
方法,而这个方法和 run()
方法放在一起。这就意味着使用这个类的人需要自己去实现和维护这个方法,这样会增加他们的负担。
在Python中,除了 __exit__
之外,其他的析构函数其实很少用,所以资源获取的代码可能和 cleanup()
的代码分开,这样就可能导致资源泄漏。
其次,你把 file_object
设成了实例变量,这样就让它的作用范围变得更大了,而不是仅限于一个函数,这样做其实也不太好。