如何正确清理Python对象?

class Package: def __init__(self): self.files = [] # ... def __del__(self): for file in self.files: os.unlink(file) 

__del__(self)失败,出现AttributeErrorexception。 我知道当__del__()被调用时, Python不能保证 “全局variables”(这个上下文中的成员数据?)的__del__() 。 如果是这种情况,这是exception的原因,我如何确保对象正常破坏?

我build议使用Python语句来pipe理需要清理的资源。 使用明确的close()语句的问题是,你不得不担心人们忘记调用它,或者忘记将它放在finally块中,以防止发生exception时资源泄漏。

要使用with语句,请使用以下方法创build一个类:

  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 

variablespackage_obj将是一个Packagetypes的实例(这是__enter__方法返回的值)。 无论是否发生exception, __exit__方法都会自动被调用。

你甚至可以采取这种方法更进一步。 在上面的例子中,有人仍然可以使用它的构造函数来实例化Package,而不使用with子句。 你不希望这样的事情发生。 你可以通过创build一个定义__enter____exit__方法的PackageResource类来解决这个问题。 然后,Package类将被严格定义在__enter__方法中并被返回。 这样,调用者永远不会实例化Package类而不使用with语句:

 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 

作为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): ... 

只需使用with Package(...) as package

为了缩短事情的时间,请将清理函数命名为close并使用contextlib.closing ,在这种情况下,您可以通过with contextlib.closing(Package(...))使用未修改的Packagewith 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) 

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

 class SubPackage(Package): def close(self): pass 

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

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

如果有更多的代码,我认为问题可能在__init__中?

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

资源

用一个try / except语句包装你的析构函数,如果你的全局variables已经被抛弃了,它不会抛出exception。

编辑

尝试这个:

 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.filesvariables(如果它被删除,那么它不会影响原始文件列表)。 如果不是即使有更多的variables引用被删除的情况下,你可以删除代理封装。

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

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

但是你应该记住,这将会持续所有创build的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... 

更好的select是使用weakref.finalize 。 使用__del __()方法查看Finalizer对象和比较终结器的示例。