使用try-finally装饰器清理Python对象

5 投票
2 回答
2415 浏览
提问于 2025-04-18 14:17

在我编程的过程中,发现对象清理是一个很常见的问题。到目前为止,我一直按照推荐的方法使用 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 个回答

2

我觉得这种做法没问题,只要你的客户端只用你的 IOObject 来执行 run 方法,而不想直接调用 connect,然后再用打开的 file_object 做其他操作。

上下文管理器让客户端很清楚地知道自己在哪里获取和清理资源,同时也给他们灵活性,可以随意使用这些资源,因为一旦他们离开 with 这个代码块,资源就会被自动清理。用这种方法就不太清楚了;客户端需要查看代码(或者文档)才能知道 run 方法会为他们清理资源,但如果直接使用 connect,就需要手动调用 cleanup 来正确清理。

还有一个Python的原则叫做“显式优于隐式”。with 语句明确地告诉客户端在哪里获取和释放资源。用装饰器的方法就失去了这一点。

3

让我来指出几个问题。

首先,你要求你的类里必须有一个 cleanup() 方法,而这个方法和 run() 方法放在一起。这就意味着使用这个类的人需要自己去实现和维护这个方法,这样会增加他们的负担。

在Python中,除了 __exit__ 之外,其他的析构函数其实很少用,所以资源获取的代码可能和 cleanup() 的代码分开,这样就可能导致资源泄漏。

其次,你把 file_object 设成了实例变量,这样就让它的作用范围变得更大了,而不是仅限于一个函数,这样做其实也不太好。

撰写回答