我一直在测试受此http://docs.python.org/2/library/contextlib.html启发的肮脏黑客攻击。 主要思想是将try / finally想法带入类级别,并获得可靠而简单的类析构函数。
class Foo():
def __init__(self):
self.__res_mgr__ = self.__acquire_resources__()
self.__res_mgr__.next()
def __acquire_resources__(self):
try:
# Acquire some resources here
print "Initialize"
self.f = 1
yield
finally:
# Release the resources here
print "Releasing Resources"
self.f = 0
f = Foo()
print "testing resources"
print f.f
但它总是给我:
Initialize
testing resources
1
永远不会“释放资源”。我的希望是:
从Python 2.5版开始,现在允许使用yield语句 尝试一个尝试的子句...终于构造。如果发电机不是 在最终确定之前恢复(通过达到零参考计数或 通过垃圾收集),generator-iterator的close()方法 将被调用,允许任何挂起的finally子句执行。 Source link
但似乎当类成员与类一起被垃圾收集时,它们的引用计数不会减少,因此结果生成器close(),因此最终永远不会被调用。至于报价的第二部分
“或通过垃圾收集”
我只是不知道为什么不是这样。有机会让这个乌托邦有效吗? :)
BTW这适用于模块级别:
def f():
try:
print "ack"
yield
finally:
print "release"
a = f()
a.next()
print "testing"
输出将如我所料:
ack
testing
release
注意:在我的任务中,我无法使用WITH管理器,因为我在线程的end_callback中释放资源(它将不在任何WITH中)。所以我希望得到一个可靠的析构函数,用于因某些原因不会调用回调的情况
答案 0 :(得分:3)
您遇到的问题是由您的生成器上定义的引用周期和隐式__del__
引起的(它是如此隐式,CPython doesn't actually show __del__
when you introspect, because only the C level tp_del
exists, no Python-visible __del__
is created)。基本上,当生成器内部有yield
时:
try
块,或等效with
阻止它有一个隐含的__del__
- 类似的实现。在Python 3.3及更早版本中,如果引用循环包含一个对象,其类实现__del__
(技术上,CPython中有tp_del
),除非循环被手动破坏,循环垃圾收集器无法清除它,并将其粘贴在gc.garbage
(import gc
以获取访问权限),因为它不知道必须首先收集哪些对象(如果有的话)才能“很好地”清理。
由于您的班级__acquire_resources__(self)
包含对实例self
的引用,因此您会形成一个参考周期:
self
- > self.__res_mgr__
(生成器对象) - >生成器框架(引用包括的本地) - > self
由于此参考周期以及生成器中包含try
/ finally
(创建tp_del
等效于__del__
)的事实,循环无法收集,除非你手动推进finally
(这违背了整个目的),否则你的self.__res_mgr__
块永远不会被执行。
您的实验恰好会自动显示此问题,因为引用周期是隐式/自动的,但是周期中的对象具有__del__
类的任何意外引用周期都会触发相同的问题,所以即使您刚做了:
class Foo():
def __init__(self):
# Acquire some resources here
print "Initialize"
self.f = 1
def __del__(self):
# Release the resources here
print "Releasing Resources"
self.f = 0
如果所涉及的“资源”可能导致带有Foo
实例的参考周期,则您会遇到同样的问题。
此处的解决方案是以下一项或两项:
with
块),并为{close
提供明确的清理方法(例如with
) 1}}块不可行(通过自己的资源管理清理的另一个对象的状态的一部分)。这也是在大多数非CPython解释器上提供确定性清理的唯一方法,其中从未使用引用计数语义(因此所有终结器都被称为非确定性,如果有的话)tp_del
的第三方扩展而不是更新以使用允许正确清理循环垃圾的tp_finalize
槽。它仍然是非确定性的清理(如果存在参考周期,你正在等待循环gc运行,有时),但它可能,其中3.4之前的这种循环垃圾不能完全清理。