解释器何时释放模块范围变量引用?

时间:2013-08-12 21:40:27

标签: python module weak-references resource-cleanup

我正在尝试在我拥有的实用程序模块中实现清理例程。在寻找我的问题的解决方案时,我最终决定使用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将以可靠地触发回调的方式发布。任何人都可以指向一些可以为我解决这个问题的文档吗?

2 个答案:

答案 0 :(得分:1)

退出时会清除Python模块,并且可能会调用任何__del__ methods

  

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

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

  

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

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

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

答案 1 :(得分:1)

在这里做的正确的事情是在某个时候明确地释放你的强引用,而不是依靠关闭来做它。

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

  

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

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

您可以使用atexit处理程序在您自己之后明确清理,但您可以在脱离主模块结束之前明确地进行清理(或调用sys.exit,或者完成最后一次非守护线程,或其他)。最简单的做法通常是将整个main函数包含在withtry / finally中。

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


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

  

我实际上要做的是关闭一个通常由计时器保持打开的子进程,但需要在程序退出时进行核心处理。是唯一真正“可靠”的方法来启动守护进程子进程来分别监视和终止其他进程吗?

执行此类操作的常用方法是使用可从外部签名的内容替换计时器。不知道您的应用程序架构和您正在使用的计时器类型(例如,反应器启动计时器的单线程异步服务器与单线程异步GUI应用程序,其中OS计时器消息启动计时器与多计时器 - 线程应用程序,其中计时器只是sleep间隔与...之间的线程,但很难更具体地解释。

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


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

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

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

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


当然,无法保证您的清理代码仍然。例如,未处理的信号(在Unix上)或结构化异常(在Windows上)将终止解释器而不给它机会清理任何东西。即使你为此编写处理程序,也有人可以随时拔下电源线。因此,如果您需要一个完全健壮的设计,您需要在不进行任何清理的情况下进行中断(通过日记,使用原子文件操作,具有显式确认的协议等)。