【问题标题】:Python destructor basing on try/finally + yield?基于try / finally + yield的Python析构函数?
【发布时间】:2014-02-13 08:14:00
【问题描述】:

我一直在测试一个受 http://docs.python.org/2/library/contextlib.html 启发的肮脏 hack。 主要思想是将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 语句现在允许在 try 子句的 try ... finally 构造。如果发电机不是 在最终确定之前恢复(通过达到零引用计数或 通过被垃圾收集),生成器迭代器的 close() 方法 将被调用,允许执行任何未决的 finally 子句。 Source link

但似乎当类成员与类一起被垃圾收集时,它们的引用计数不会减少,因此生成器 close() 并且因此永远不会调用 finally。至于报价的第二部分

“或者被垃圾回收”

我只是不知道为什么这不是真的。有没有机会让这个乌托邦发挥作用? :)

顺便说一句,这适用于模块级别:

def f():
  try:
    print "ack"
    yield
  finally:
    print "release"

a = f()
a.next()
print "testing"

输出将如我所料:

ack
testing
release

注意:在我的任务中,我无法使用 WITH 管理器,因为我正在释放线程的 end_callback 内的资源(它将不在任何 WITH 中)。所以我想在由于某种原因不会调用回调的情况下获得一个可靠的析构函数

【问题讨论】:

  • 不能保证程序结束时剩下的任何东西都会被垃圾回收,也不能保证它们的析构函数会运行。
  • @user2357112,那么为什么这总是在模块级别上有效,而在课堂上却没有呢?
  • 任意实施决策。你不应该依赖任何一个版本。
  • 与仅仅定义__del__相比,您希望从这些扭曲中获得什么?
  • 嗯,它不是,主要是因为它只是 __del__ 的花哨语法。生成器也是对象,必须通过与其他对象相同的方式完成。

标签: python garbage-collection generator destructor yield


【解决方案1】:

您遇到的问题是由在您的生成器上定义的引用循环和隐式__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__-like 实现。在 Python 3.3 和更早的版本中,如果引用循环包含一个对象,其类实现了 __del__(技术上,在 CPython 中具有 tp_del),除非循环被手动中断,否则循环垃圾收集器无法清理它,只会将其粘贴在gc.garbageimport 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 的实例,那么您将遇到同样的问题。

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

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

【讨论】:

    猜你喜欢
    • 2012-01-24
    • 1970-01-01
    • 2011-09-02
    • 2018-03-19
    • 2011-01-21
    • 1970-01-01
    • 2018-02-24
    • 2023-03-09
    • 2014-11-27
    相关资源
    最近更新 更多