如何正确清理Python对象?
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