【问题标题】:When does a module scope variable reference get released by the interpreter?解释器何时释放模块范围变量引用?
【发布时间】:2013-08-14 08:41:55
【问题描述】:

我正在尝试在我拥有的实用程序模块中实现清理例程。在四处寻找解决问题的方法时,我最终决定使用weakref 回调来进行清理。但是,我担心它不会按预期工作,因为在同一个模块中对对象的强引用。举例说明:

foo_lib.py

class Foo(object):

    _refs = {}

    def __init__(self, x):

        self.x = x
        self._weak_self = weakref.ref(self, Foo._clean)
        Foo._refs[self._weak_self] = x

    @classmethod
    def _clean(cls, ref):

        print 'cleaned %s' % cls._refs[ref]

foo = Foo()

然后其他类引用foo_lib.foo。我确实从 1.5.1 中找到了一份旧文档,其中提到了我的担忧 (http://www.python.org/doc/essays/cleanup/),但没有什么能让我完全放心 foo 将以可靠触发回调的方式发布。任何人都可以向我指出一些可以为我解决这个问题的文档吗?

【问题讨论】:

  • 我认为你不想依赖弱引用清理(或者,等效地,__del__)来完成你真正在做的事情,而且很难给出一个可靠的答案......但它绝对是一个有趣的问题,感谢您让我了解解释器最终确定的实际作用,因为那里也有一些有趣的东西。

标签: python module weak-references resource-cleanup


【解决方案1】:

正确的做法是在某个时候明确释放您的强引用,而不是指望关机来做到这一点。

特别是,如果模块被释放,它的全局变量也将被释放……但似乎没有记录任何模块将被释放的地方。因此,在关闭时可能仍然存在对您的对象的引用。而且,正如 Martijn Pieters 指出的那样:

不能保证在解释器退出时仍会为仍然存在的对象调用 __del__() 方法。

但是,如果您可以确保在解释器退出之前的某个时间没有(非弱)引用您的对象,则可以保证您的清理运行。

您可以使用atexit 处理程序在自己之后显式清理,但您可以在脱离主模块末尾之前显式执行此操作(或调用sys.exit,或完成最后一个非守护线程,或任何)。最简单的做法通常是将整个main 函数包装在withtry/finally 中。

或者,更简单地说,不要尝试将清理代码放入__del__ 方法或weakref 回调中;只需将清理代码本身放入您的withfinallyatexit


在对另一个答案的评论中:

我实际上想要做的是关闭一个通常由计时器保持打开的子进程,但需要在程序退出时进行核对。是否是唯一真正“可靠”的方式来启动一个守护子进程来分别监视和杀死另一个进程?

做这种事情的通常方法是用可以从外部发出信号的东西来代替计时器。在不知道您的应用程序架构和您正在使用哪种计时器的情况下(例如,反应器启动计时器的单线程异步服务器与操作系统计时器消息启动计时器的单线程异步 GUI 应用程序与多-线程应用程序,其中计时器只是sleeps 在间隔与...之间的线程),很难更具体地解释。

同时,您可能还想看看是否有更简单的方法来处理您的子流程。例如,可能使用显式进程组,并杀死您的进程组而不是您的进程(这将杀死所有子进程,在 Windows 和 Unix 上......尽管细节非常不同)?或者也许给子进程一个管道并在管道的另一端出现故障时让它退出?


请注意,文档也不保证删除剩余引用的顺序(如果有的话)。事实上,如果您使用的是 CPython,Py_Finalize 会明确表示它是“按随机顺序完成的”。

The source 很有趣。它显然不是明确随机的,甚至不是完全任意的。首先它进行 GC 收集,直到什么都没有留下,然后它最终确定 GC 本身,然后它执行 PyImport_Cleanup(基本上只是 sys.modules.clear()),然后有另一个收集被注释掉(讨论了为什么),并且最后是_PyImport_Fini(仅定义为“仅供内部使用”)。

但这意味着,假设您的模块确实持有对您的对象的唯一(非弱)引用,并且没有涉及模块本身的牢不可破的循环,那么您的模块将在关闭时被清理,这将删除对您的对象的最后一个引用,从而使其也被清理。 (当然,除了内置函数、扩展模块和你现在仍然存在的直接引用的东西之外,你不能指望任何东西......但你上面的代码应该没问题,因为foo不能在@987654342之前被清理@,并且它不依赖任何其他非内置函数。)

请记住,这是特定于 CPython 的——实际上是特定于 CPython 3.3 的;您将需要阅读您的版本的相关等效源以确保。同样,文档明确指出,事情会“以随机顺序”被删除,因此如果您不想依赖特定于实现的行为,那么这就是您所期望的。


当然,您的清理代码仍然不能保证被调用。例如,未处理的信号(在 Unix 上)或结构化异常(在 Windows 上)将杀死解释器,而不给它清理任何东西的机会。即使您为此编写处理程序,也总是有人会拉电源线。因此,如果您需要一个完全健壮的设计,您需要在任何时候都无需清理即可中断(通过日志、使用原子文件操作、具有显式确认的协议等)。

【讨论】:

  • 这在我的代码中实际上是在管理测试框架中的性能优化。我有一个虚拟显示管理器,可以跨多个测试启动和停止单个虚拟显示实例,并跟踪其他代码保留和释放显示。在没有用户使用简单的计时器线程保留显示的 4 分钟后,计时器将终止虚拟显示。但是,如果鼻子退出测试套件时还不到 4 分钟,则管理器会保持显示处于活动状态(如果监视器是守护进程)或保持进程打开直到线程超时。
  • 一开始我实际上是在看atexit,但我认为如果能更聪明一点会更好。也许我会变得不那么聪明。 :)
  • “随机”顺序源于清理顺序与字典相关联的事实;引入了 3.3 随机散列,使其更加随机。请参阅stackoverflow.com/questions/18163697/… 了解随机化导致“奇怪”行为的示例。
  • @MartijnPieters:这是有道理的……除了 2.6 到 3.2 的文档中也有关于“随机顺序”的内容。我怀疑这只是一个 CYA 术语,旨在阻止您依赖您碰巧发现的任何订单。 (在以前的版本中,即使在错误修复版本中也有变化,这会改变这个顺序。)但你是对的,大部分清理工作是由大部分 sys.modules 以字典迭代顺序清理驱动的,这在 3.3 中更加随机……
【解决方案2】:

Python 模块在退出时被清理,任何__del__ methods可能都会被调用:

不能保证在解释器退出时为仍然存在的对象调用__del__() 方法。

以下划线开头的名称被清除首先

从 1.5 版开始,Python 保证名称以下划线开头的全局变量会在其他全局变量被删除之前从其模块中删除;如果不存在对此类全局变量的其他引用,这可能有助于确保在调用 __del__() 方法时导入的模块仍然可用。

弱引用回调依赖于与__del__ 方法相同的机制; C 释放函数 (type->tp_dealloc)。

foo 实例将保留对 Foo._clean 类方法的引用,但 全局 名称 Foo 可能已经被清除(在 CPython 中分配为 None);您的方法应该是安全的,因为一旦注册回调,它就永远不会引用Foo

【讨论】:

  • 我特别避免__del__因为所有的警告。我遇到了code.activestate.com/recipes/…del-and-without-ha/,这让我觉得weakref 的方向是正确的,但正如我所说,无法找到任何澄清有关何时发布参考的信息。 weakref 回调真的和 __del__ 一样可靠吗?
  • 调用weakref回调的机制与调用__del__的机制相同。
  • 文档在哪里保证退出时模块实际上被清理了?这将是一个有用的链接。
  • @abarnert:除了object.__del__ 文档中提到的_ 单下划线名称被首先清除之外,我还没有找到任何东西。
  • 嗯。嗯,这很不幸。所以我实际上想要做的是关闭一个通常由计时器保持打开的子进程,但在程序退出时需要被核对。是唯一真正“可靠”的方法来启动一个守护子进程来分别监视和杀死另一个进程吗?
猜你喜欢
  • 2016-03-20
  • 2010-09-11
  • 1970-01-01
  • 2018-10-05
  • 2013-07-01
  • 2018-01-18
  • 2010-10-09
  • 1970-01-01
  • 2019-09-11
相关资源
最近更新 更多