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

    # ...

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

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


当前回答

我认为问题可能是在__init__如果有更多的代码比显示?

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

其他回答

atexit。在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的垃圾收集启动超出其生命周期的对象时,将首先被销毁。

标准的方法是使用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...

一个好主意是将两种方法结合起来。

实现用于显式生命周期处理的上下文管理器。以及句柄清理,以防用户忘记它或不方便使用with语句。这最好由weakref.finalize来完成。

这是很多图书馆实际做的。根据严重程度,你可以发出警告。

它保证只被调用一次,所以在之前的任何时候调用它都是安全的。

import os
from typing import List
import weakref

class Package:
    def __init__(self):
        self.files = []
        self._finalizer = weakref.finalize(self, self._cleanup_files, self.files)

    @staticmethod
    def _cleanup_files(files: List):
        for file in files:
            os.unlink(file)

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self._finalizer()

weakref。Finalize返回一个可调用的终结器对象,该对象将在obj被垃圾收集时被调用。与普通的弱引用不同,终结器将始终存在,直到引用对象被收集,从而极大地简化了生命周期管理。”

不像atexit。寄存器对象在解释器关闭之前不会保存在内存中。

不像物体。__del__ weakref。Finalize保证在解释器关闭时被调用。所以更安全。

作为Clint回答的附录,你可以使用contextlib.contextmanager简化PackageResource:

@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):
        ...

简单地使用Package(…)作为Package。

为了更简洁,请将清理函数命名为close并使用contextlib。关闭,在这种情况下,你可以使用未修改的Package类via 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

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