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

    # ...

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

__del__(self)以上失败并出现 AttributeError 异常。我了解Python 不保证__del__()调用时存在“全局变量”(此上下文中的成员数据?) 。如果是这种情况并且这是异常的原因,我如何确保对象正确销毁?

4

10 回答 10

717

我建议使用 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__无论是否发生异常,都会自动调用其方法。

您甚至可以将这种方法更进一步。在上面的示例中,仍然可以使用其构造函数实例化 Package 而无需使用with子句。你不希望这种情况发生。您可以通过创建定义__enter____exit__方法的 PackageResource 类来解决此问题。然后,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
于 2009-05-14T19:39:56.287 回答
84

标准方法是使用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...
于 2017-01-13T03:48:57.130 回答
37

作为克林特答案的附录,您可以简化PackageResource使用contextlib.contextmanager

@contextlib.contextmanager
def packageResource():
    class Package:
        ...
    package = Package()
    yield package
    package.cleanup()

或者,虽然可能不像 Pythonic,但您可以覆盖Package.__new__

class Package(object):
    def __new__(cls, *args, **kwargs):
        @contextlib.contextmanager
        def packageResource():
            # adapt arguments if superclass takes some!
            package = super(Package, cls).__new__(cls)
            package.__init__(*args, **kwargs)
            yield package
            package.cleanup()

    def __init__(self, *args, **kwargs):
        ...

并简单地使用with Package(...) as package.

为了让事情更短,命名你的清理函数close并使用contextlib.closing,在这种情况下,你可以使用未修改的Package类,with contextlib.closing(Package(...))或者将其覆盖__new__为更简单的

class Package(object):
    def __new__(cls, *args, **kwargs):
        package = super(Package, cls).__new__(cls)
        package.__init__(*args, **kwargs)
        return contextlib.closing(package)

而且这个构造函数是继承的,所以你可以简单地继承,例如

class SubPackage(Package):
    def close(self):
        pass
于 2015-05-20T12:11:21.680 回答
35

更好的选择是使用weakref.finalize。请参阅终结器对象使用 __del__() 方法比较终结器中的示例。

于 2017-03-20T15:37:41.807 回答
18

__del__我认为在调用之前不可能删除实例成员。我的猜测是您的特定 AttributeError 的原因在其他地方(也许您错误地在其他地方删除了 self.file )。

但是,正如其他人指出的那样,您应该避免使用__del__. 这样做的主要原因是实例__del__不会被垃圾收集(它们只会在它们的引用计数达到 0 时被释放)。因此,如果您的实例涉及循环引用,只要应用程序运行,它们就会一直存在于内存中。(虽然我可能对这一切都误解了,我必须再次阅读 gc 文档,但我很确定它是这样工作的)。

于 2009-05-14T19:51:55.367 回答
15

__init__我认为如果代码比显示的多,问题可能出在哪里?

__del__即使__init__没有正确执行或抛出异常也会被调用。

来源

于 2012-11-29T08:22:09.730 回答
14

这是一个最小的工作框架:

class SkeletonFixture:

    def __init__(self):
        pass

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        pass

    def method(self):
        pass


with SkeletonFixture() as fixture:
    fixture.method()

重要:返回自我


如果你像我一样,忽略了return self克林特米勒的正确答案的部分),你会盯着这个胡说八道:

Traceback (most recent call last):
  File "tests/simplestpossible.py", line 17, in <module>                                                                                                                                                          
    fixture.method()                                                                                                                                                                                              
AttributeError: 'NoneType' object has no attribute 'method'

希望它可以帮助下一个人。

于 2018-04-16T14:10:21.250 回答
8

只需用 try/except 语句包装你的析构函数,如果你的全局变量已经被处理掉,它就不会抛出异常。

编辑

试试这个:

from weakref import proxy

class MyList(list): pass

class Package:
    def __init__(self):
        self.__del__.im_func.files = MyList([1,2,3,4])
        self.files = proxy(self.__del__.im_func.files)

    def __del__(self):
        print self.__del__.im_func.files

它将在调用时保证存在的del函数中填充文件列表。weakref 代理是为了防止 Python,或者你自己以某种方式删除 self.files 变量(如果被删除,那么它不会影响原始文件列表)。如果即使有更多对变量的引用也没有删除它,那么您可以删除代理封装。

于 2009-05-14T19:08:42.307 回答
6

似乎这样做的惯用方法是提供一个close()方法(或类似方法),并明确地调用它。

于 2009-05-14T19:09:24.753 回答
2

atexit.register是ostrakach 的回答中已经提到的标准方式。

但是,必须注意,不能依赖对象可能被删除的顺序,如下面的示例所示。

import atexit

class A(object):

    def __init__(self, val):
        self.val = val
        atexit.register(self.hello)

    def hello(self):
        print(self.val)


def hello2():
    a = A(10)

hello2()    
a = A(20)

在这里,与创建对象的顺序相反,顺序似乎是合法的,因为程序给出的输出为:

20
10

但是,当在一个更大的程序中,python 的垃圾回收器在对象的生命周期之外启动时会首先被破坏。

于 2020-09-19T15:59:09.293 回答