基于 try/finally + yield 的 Python 析构函数?

标签 python garbage-collection generator destructor yield

我一直在测试受此 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 实例的引用循环,那么您也会遇到同样的问题。

这里的解决方案是以下一种或两种:

  1. 上课 a context manager因此用户提供确定性终结所需的信息(通过使用 with block )以及为 when with 提供明确的清理方法(例如 close) > block 不可行(另一个对象的状态的一部分,通过它自己的资源管理清除)。这也是在从未使用过引用计数语义的大多数非 CPython 解释器上提供确定性清理的唯一方法(因此所有终结器都被非确定性地调用,如果有的话)
  2. 迁移到 Python 3.4 或更高版本,其中 PEP 442解决了无法回收的循环垃圾的问题(从技术上讲,在 CPython 上仍然可以产生这样的循环,但只能通过第三方扩展继续使用 tp_del 而不是更新为使用 tp_finalize 允许循环垃圾被正确清理的插槽)。它仍然是非确定性清理(如果存在引用循环,您有时会等待循环 gc 运行),但它可能,在 3.4 之前,这种循环垃圾不可能完全清理干净。

关于基于 try/finally + yield 的 Python 析构函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21297026/

相关文章:

python - 将索引上的数据框与 Pandas 合并

java - 无法打印 GC 详细信息

ruby-on-rails - 覆盖默认的 Rails 模型模板

python - 如何使用生成器参数验证 Python 模拟调用

python - 从整个模块中可用的子模块创建全局变量

python - Sublime Text Anaconda 更新后出错

Java 1.7 - CMS 在达到 CMSInitiatingOccupancyFraction 之前启动

javascript - 将对象转换为 JSON 字符串并将其置空

python 3 : Reverse consecutive runs in sorted list?

python - 如何将 2d numpy 数组的一行作为 2d 数组