2

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

4

2 回答 2

1

Python 模块在退出时会被清理,并且可能会调用任何__del__方法:

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

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

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

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

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

于 2013-08-12T21:42:19.737 回答
1

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

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

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

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

您可以使用atexit处理程序在自己之后显式清理,但您可以在脱离主模块(或调用sys.exit,或完成最后一个非守护线程,或其他)之前显式地进行清理。最简单的做法通常是获取整个main函数并将其包装在 a withor try/finally中。

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


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

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

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

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


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

来源很有趣。它显然不是明确随机的,甚至不是完全任意的。首先它进行 GC 收集,直到什么都没有留下,然后它最终确定 GC 本身,然后它执行 a (PyImport_Cleanup基本上sys.modules.clear()就是_PyImport_Fini仅作为“仅供内部使用”)。

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

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


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

于 2013-08-12T22:03:51.700 回答