1 2 3 4 5 6 7 8 9
| class Package:
def __init__(self):
self.files = []
# ...
def __del__(self):
for file in self.files:
os.unlink(file) |
上面的__del__(self)失败,出现attributeError异常。我理解python不能保证"全局变量"(此上下文中的成员数据)的存在。当调用__del__()时。如果是这种情况,这就是异常的原因,那么如何确保对象正确销毁?
- 在阅读你链接的内容时,全局变量消失在这里似乎不适用,除非你在谈论程序何时退出,在此期间,我根据你链接的内容猜测,操作系统模块本身可能已经消失了。否则,我认为它不适用于uu del_uu()方法中的成员变量。
- 异常在我的程序退出之前就被抛出了。我得到的attributeError异常是python说它不能将self.files识别为包的属性。我可能弄错了,但是如果"全局"不是指方法的全局变量(但可能是类的局部变量),那么我不知道是什么导致了这个异常。google hints python保留在调用"self"之前清理成员数据的权利。
- 发布的代码似乎对我有用(使用Python2.5)。您可以发布失败的实际代码,还是发布一个简化的代码(越简单,仍然会导致错误的更好版本)?
- @你能举个更具体的例子吗?在我所有的测试中,del析构函数都是完美的。
- 这个类与我发布的没有太大的不同,但是它被用于一个更大的代码块中,这个代码块太大了,以至于这些页边距不能包含…我将尝试找到触发这个的最小代码。
- Wilhelmtell检查我的最新更新
- 如果有人想知道:本文阐述了为什么不应将__del__用作__init__的对应物。(也就是说,在__init__是一个构造函数的意义上,它不是一个"析构函数"。
- 底线:Python不支持像C++和其他语言一样的RAII(也就是LIFO对象破坏)。参考资料:python docs、pep 343、wikibooks for python"python不支持raii"。
我建议使用python的with语句来管理需要清理的资源。使用显式close()语句的问题是,您必须担心人们忘记调用它,或者忘记将它放在finally块中,以防止发生异常时发生资源泄漏。
要使用with语句,请使用以下方法创建一个类:
1 2
| def __enter__(self)
def __exit__(self, exc_type, exc_value, traceback) |
在上面的示例中,您将使用
1 2 3 4 5 6 7 8 9 10 11 12
| 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) |
然后,当有人想使用你的课程时,他们会做以下操作:
1 2
| with Package() as package_obj:
# use package_obj |
变量包"obj"将是package类型的实例(它是__enter__方法返回的值)。它的__exit__方法将自动被调用,不管是否发生异常。
您甚至可以进一步采用这种方法。在上面的示例中,仍有人可以使用其构造函数实例化包,而不必使用with子句。你不想这样。您可以通过创建定义__enter__和__exit__方法的packageresource类来解决这个问题。然后,package类将严格定义在__enter__方法内并返回。这样,调用方就无法在不使用with语句的情况下实例化package类:
1 2 3 4 5 6 7 8 9
| 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() |
您可以按如下方式使用:
1 2
| with PackageResource() as package_obj:
# use package_obj |
- 从技术上讲,可以显式地调用packageresource()并创建一个永远不会完成的包…但他们真的必须尝试破解代码。可能没什么好担心的。
- 顺便说一下,如果您使用的是python 2.5,那么您需要在以后的import with_语句中使用with语句。
- 我找到了一篇文章,有助于说明为什么uudel uuu()按照它的方式行事,并相信使用上下文管理器解决方案:andy pearce.com/blog/posts/2013/apr/python destructor drawba‌&8203;cks
- 如果您想传递参数,如何使用这个漂亮干净的构造?我想能做一个EDOCX1[0]
- @snooze92您可以给资源一个uuu init_uuu方法,该方法本身存储*args和**kwargs,然后在enter方法中将它们传递给内部类。当使用WITH语句时,在输入之前调用_uuu init_uuu__
- @Brianschlenker这几乎就是我最终要做的。从编译后的编程背景来看,我只希望给__init__方法提供明确的参数,以使我的"接口"更清晰。它更像是"Python"还是我在使用*args和**kwargs时没有看到的优势?我知道,它只会在解释时失败,但也许对资源有一个更清晰的__init__签名会使拥有一个好的IDE的程序员更容易使用?
- @打盹92这真的取决于你想做什么,我不认为一种方式比另一种方式更像Python。假设您正在包装一个接受了许多参数的函数,并且不想在包装器中复制所有这些参数。这时我将使用*args和**kwargs来简化编程过程。在您的例子中,我可能也更喜欢对参数进行显式处理。
- 这个答案是否意味着每次我们必须处理python中的资源(文件、db等),我们必须创建一个包装类而不仅仅是一个析构函数?
- 如果Package是一个(抽象的)基类,这个模式看起来会怎样?有没有从内部阶级继承的方法?
- 很好。)使用contextlib可以简化PackageResource,请看我的答案。
- …或者更好,如果清除命名为close,只需使用contextlib.closing。
- __exit__和__del__有什么区别?
- 当某个对象的生存期在某些语法框架之外时,很难使用with。但与其他发生的事件有关。
标准方法是使用atexit.register:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| # 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的代码演示:
1 2 3 4 5 6 7 8 9 10 11
| $ 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... |
- atexit.register方法的好处在于,您不必担心类的用户会做什么(他们是否使用了with)?他们有没有明确地打电话给__enter__?当然,缺点是,如果您需要在Python退出之前进行清理,那么它将不起作用。在我的例子中,我不关心对象何时超出范围,或者在Python退出之前是否超出范围。:)
作为克林特答案的附录,您可以使用contextlib.contextmanager简化PackageResource:
1 2 3 4 5 6 7
| @contextlib.contextmanager
def packageResource():
class Package:
...
package = Package()
yield package
package.cleanup() |
或者,虽然可能不是Python,但您可以覆盖Package.__new__:
1 2 3 4 5 6 7 8 9 10 11 12
| 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(...))使用未修改的Package类,也可以将其__new__重写为更简单的
1 2 3 4 5
| class Package(object):
def __new__(cls, *args, **kwargs):
package = super(Package, cls).__new__(cls)
package.__init__(*args, **kwargs)
return contextlib.closing(package) |
这个构造函数是继承的,所以您可以简单地继承,例如
1 2 3
| class SubPackage(Package):
def close(self):
pass |
- 这太棒了。我特别喜欢最后一个例子。然而,不幸的是,我们无法避免Package.__new__()方法的四行样板。或者我们可以。我们可以为我们定义一个类修饰器或者为样板文件定义元类泛型。为Python思维提供食物。
- @塞西尔科里,谢谢,说得对。从Package继承的任何类也应该这样做(尽管我还没有测试过),所以不需要元类。虽然我在过去发现了一些非常奇怪的使用元类的方法…
- @实际上,构造函数是继承的,所以可以使用Package(或者更好的是名为Closing的类)作为类的父类,而不是object。但不要问我多重继承是怎么搞砸的…
我不认为有可能在调用__del__之前删除成员。我的猜测是,您的特定属性错误的原因是在其他地方(可能您在其他地方错误地删除self.file)。
然而,正如其他人指出的,您应该避免使用__del__。这主要是因为使用__del__的实例不会被垃圾收集(只有当引用计数达到0时才会释放)。因此,如果您的实例涉及循环引用,那么它们将在应用程序运行期间一直存在于内存中。(不过,我可能对这一切都弄错了,我必须再次阅读GC文档,但我确信它是这样工作的)。
- 如果来自其他具有__del__的对象的引用计数为零且不可访问,则使用__del__的对象可以被垃圾收集。这意味着,如果使用__del__的对象之间有一个引用循环,则不会收集这些对象。但是,任何其他情况都应按预期解决。
我认为如果代码比显示的多,问题可能出在__init__中?
即使__init__未被正确执行或抛出异常,也将调用__del__。
来源
- 听起来很有可能。在使用__del__时,避免这个问题的最好方法是在类级别显式声明所有成员,确保它们始终存在,即使__init__失败。在给定的例子中,files = ()是有效的,尽管大多数情况下您只需分配None;在这两种情况下,您仍然需要分配__init__中的实际值。
更好的选择是使用weakref.finalize。请参阅Finalizer对象中的示例,并将Finalizer与uu del_uu()方法进行比较。
- 今天使用了它,它的工作完美无瑕,比其他解决方案更好。我有基于多处理的Communicator类,它打开一个串行端口,然后我有一个stop()方法来关闭端口和join()进程。但是,如果程序意外退出,那么不会调用stop()—我用终结器解决了这个问题。但无论如何,我在stop方法中调用_finalizer.detach(),以防止调用它两次(由终结器手动调用,稍后再调用)。
- 依我看,这真是最好的答案。它结合了垃圾收集时清理的可能性和出口处清理的可能性。警告是,python2.7没有weakref.finalize。
只需用try/except语句包装析构函数,如果已经释放了全局变量,则不会引发异常。
编辑
试试这个:
1 2 3 4 5 6 7 8 9 10 11
| 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变量(如果删除了该变量,则不会影响原始文件列表)。如果没有这种情况,即使对变量有更多的引用,也要删除它,那么您可以删除代理封装。
- 问题是,如果成员数据不在,我就太晚了。我需要那个数据。请参见上面的代码:我需要文件名来知道要删除哪些文件。不过,我简化了代码,还有其他数据需要我自己清理(即,解释器不知道如何清理)。
- 这个解决方案在我的情况下非常有效。
这里是一个最小的工作框架:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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部分,你就会盯着这胡说八道:
1 2 3 4
| Traceback (most recent call last):
File"tests/simplestpossible.py", line 17, in <module>
fixture.method()
AttributeError: 'NoneType' object has no attribute 'method' |
我为此花了半天时间。希望它能帮助下一个人。
似乎惯用的方法是提供一个close()方法(或类似方法),并明确地调用它。
- 这是我以前使用的方法,但我遇到了其他问题。除了其他库抛出的异常之外,我需要python的帮助来清理出错时的混乱。具体地说,我需要python为我调用析构函数,因为否则代码会很快变得无法管理,我肯定会忘记一个出口点,在这里应该调用.close()。