我一直在测试受此 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
永远不要“释放资源”。我的希望基于:
As of Python version 2.5, the yield statement is now allowed in the try clause of a try ... finally construct. If the generator is not resumed before it is finalized (by reaching a zero reference count or by being garbage collected), the generator-iterator’s close() method will be called, allowing any pending finally clauses to execute. Source link
但似乎当类成员与类一起被垃圾回收时,它们的引用计数不会减少,因此生成器 close() 和 finally 永远不会被调用。至于报价的第二部分
"or by being garbage collected"
我只是不知道为什么这不是真的。有没有机会让这个乌托邦发挥作用? :)
顺便说一句,这适用于模块级别:
def f():
try:
print "ack"
yield
finally:
print "release"
a = f()
a.next()
print "testing"
输出将如我所料:
ack
testing
release
注意:在我的任务中,我无法使用 WITH 管理器,因为我在线程的 end_callback 内释放资源(它将脱离任何 WITH)。所以我想获得一个可靠的析构函数,以应对由于某种原因不会调用回调的情况
最佳答案
您遇到的问题是由引用循环和生成器上定义的隐式 __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
block ,或等效的with
block
它有一个类似于__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
block 永远不会被执行,除非你手动推进 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
实例的引用循环,那么您也会遇到同样的问题。
这里的解决方案是以下一种或两种:
- 上课 a context manager因此用户提供确定性终结所需的信息(通过使用
with
block )以及为 whenwith
提供明确的清理方法(例如close
) > block 不可行(另一个对象的状态的一部分,通过它自己的资源管理清除)。这也是在从未使用过引用计数语义的大多数非 CPython 解释器上提供确定性清理的唯一方法(因此所有终结器都被非确定性地调用,如果有的话) - 迁移到 Python 3.4 或更高版本,其中 PEP 442解决了无法回收的循环垃圾的问题(从技术上讲,在 CPython 上仍然可以产生这样的循环,但只能通过第三方扩展继续使用
tp_del
而不是更新为使用tp_finalize
允许循环垃圾被正确清理的插槽)。它仍然是非确定性清理(如果存在引用循环,您有时会等待循环 gc 运行),但它可能,在 3.4 之前,这种循环垃圾不可能完全清理干净。
关于基于 try/finally + yield 的 Python 析构函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21297026/