如何正确清理Python对象?

607 投票
11 回答
574272 浏览
提问于 2025-04-15 11:35
class Package:
    def __init__(self):
        self.files = []

    # ...

    def __del__(self):
        for file in self.files:
            os.unlink(file)

上面提到的__del__(self)在执行时出现了一个属性错误(AttributeError)。我明白Python并不能保证在调用__del__()时“全局变量”(在这里指的是成员数据)一定存在。如果真是这样,这就是导致错误的原因,那我该如何确保对象能够正确地被销毁呢?

11 个回答

52

一个更好的选择是使用 weakref.finalize。你可以在 终结器对象将终结器与 __del__() 方法进行比较 的例子中看到更多内容。

109

标准的方法是使用 atexit.register

# package.py
import atexit
import os

class Package:
    def __init__(self):
        self.files = []
        atexit.register(self.cleanup)

    def cleanup(self):
        print("Running cleanup...")
        for file in self.files:
            print("Unlinking file: {}".format(file))
            # os.unlink(file)

但是你要记住,这样做会一直保留所有创建的 Package 实例,直到 Python 终止。

下面是一个使用上面代码的示例,保存为 package.py

$ python
>>> from package import *
>>> p = Package()
>>> q = Package()
>>> q.files = ['a', 'b', 'c']
>>> quit()
Running cleanup...
Unlinking file: a
Unlinking file: b
Unlinking file: c
Running cleanup...
770

我建议使用Python的with语句来管理需要清理的资源。使用明确的close()语句的问题在于,你得担心有人会忘记调用它,或者忘记把它放在finally块里,以防在发生异常时资源泄漏。

要使用with语句,你需要创建一个包含以下方法的类:

def __enter__(self)
def __exit__(self, exc_type, exc_value, traceback)

在你上面的例子中,你会使用

class Package:
    def __init__(self):
        self.files = []

    def __enter__(self):
        return self

    # ...

    def __exit__(self, exc_type, exc_value, traceback):
        for file in self.files:
            os.unlink(file)

然后,当有人想使用你的类时,他们会这样做:

with Package() as package_obj:
    # use package_obj

变量package_obj将是Package类型的一个实例(它是__enter__方法返回的值)。不管是否发生异常,它的__exit__方法都会自动被调用。

你甚至可以进一步改进这个方法。在上面的例子中,有人仍然可以在不使用with语句的情况下,通过构造函数来实例化Package。你不希望这样发生。你可以通过创建一个PackageResource类来解决这个问题,这个类定义了__enter____exit__方法。然后,Package类将严格在__enter__方法内部定义并返回。这样,调用者就无法在不使用with语句的情况下实例化Package类:

class PackageResource:
    def __enter__(self):
        class Package:
            ...
        self.package_obj = Package()
        return self.package_obj

    def __exit__(self, exc_type, exc_value, traceback):
        self.package_obj.cleanup()

你可以这样使用:

with PackageResource() as package_obj:
    # use package_obj

撰写回答