6

我需要注册一个atexit函数以用于一个类(参见Foo下面的示例),不幸的是,我无法通过方法调用直接清理:其他代码,我无法控制,调用Foo.start()Foo.end()但是有时遇到错误不调用Foo.end(),所以我需要自己清理。

在这种情况下,我可以使用一些关于闭包的建议:

class Foo:
  def cleanup(self):
     # do something here
  def start(self):
     def do_cleanup():
        self.cleanup()
     atexit.register(do_cleanup)
  def end(self):
     # cleanup is no longer necessary... how do we unregister?
  • 闭包能否正常工作,例如在 中do_cleanup,self 的值是否正确绑定?

  • 如何取消注册 atexit() 例程?

  • 有一个更好的方法吗?

编辑:这是 Python 2.6.5

4

5 回答 5

6

使注册表成为全局注册表和调用其中函数的函数,并在必要时从那里删除它们。

cleaners = set()

def _call_cleaners():
    for cleaner in list(cleaners):
        cleaner()

atexit.register(_call_cleaners)

class Foo(object):
  def cleanup(self):
     if self.cleaned:
         raise RuntimeError("ALREADY CLEANED")
     self.cleaned = True
  def start(self):
     self.cleaned = False
     cleaners.add(self.cleanup)
  def end(self):
     self.cleanup()
     cleaners.remove(self.cleanup)
于 2010-12-28T16:00:59.667 回答
3

我认为代码很好。无法取消注册,但您可以设置一个禁用清理的布尔标志:

    class Foo:
      def __init__(self):
         self.need_cleanup = True
      def cleanup(self):
         # do something here
         print 'clean up'
      def start(self):
         def do_cleanup():
            if self.need_cleanup:
               self.cleanup()
         atexit.register(do_cleanup)
      def end(self):
         # cleanup is no longer necessary... how do we unregister?
         self.need_cleanup = False

最后,请记住,atexit如果“程序被 Python 未处理的信号杀死、检测到 Python 致命内部错误或调用 os._exit() 时,不会调用处理程序。

于 2010-12-28T16:00:30.423 回答
3

self在回调中正确绑定到 do_cleanup,但实际上,如果您所做的只是调用该方法,您还不如直接使用绑定方法。

您使用atexit.unregister()删除回调,但这里有一个问题,因为您必须取消注册您注册的相同函数,并且因为您使用了嵌套函数,这意味着您必须存储对该函数的引用。如果您遵循我使用绑定方法的建议,那么您仍然必须保存对它的引用:

class Foo:
  def cleanup(self):
     # do something here
  def start(self):
     self._cleanup = self.cleanup # Need to save the bound method for unregister
     atexit.register(self._cleanup)
  def end(self):
     atexit.unregister(self._cleanup)

请注意,您的代码仍然有可能在不调用atexit注册函数的情况下退出,例如,如果进程在 Windows 上被 ctrl+break 中止,或者在 linux 上被 SIGABRT 杀死。

同样,正如另一个答案所暗示的那样,您可以只使用__del__,但这可能会在程序退出时进行清理,因为在删除需要访问的其他全局变量之前,它可能不会被调用。

编辑指出,当我写这个答案时,问题没有指定 Python 2.x。哦,好吧,无论如何我都会把答案留在这里,以防它帮助其他人。

于 2010-12-28T16:01:56.977 回答
3

既然shanked删了他的贴子,我再表态__del__

import atexit, weakref
class Handler:
    def __init__(self, obj):
        self.obj = weakref.ref(obj)
    def cleanup(self):
        if self.obj is not None:
            obj = self.obj()
            if obj is not None:
                obj.cleanup()

class Foo:
    def __init__(self):
        self.start()

    def cleanup(self):
        print "cleanup"
        self.cleanup_handler = None

    def start(self):
        self.cleanup_handler = Handler(self)
        atexit.register(self.cleanup_handler.cleanup)

    def end(self):
        if self.cleanup_handler is None:
            return
        self.cleanup_handler.obj = None
        self.cleanup()

    def __del__(self):
        self.end()

a1=Foo()
a1.end()
a1=Foo()
a2=Foo()
del a2
a3=Foo()
a3.m=a3

这支持以下情况:

  • 定期调用 .end 的对象;马上清理
  • 在没有调用 .end 的情况下释放的对象;最后一个引用消失时的清理
  • 生活在循环中的物体;清理 atexit
  • 保持活力的物体;清理 atexit

请注意,清理处理程序持有对对象的弱引用很重要,否则它会使对象保持活动状态。

编辑:涉及 Foo 的循环不会被垃圾收集,因为 Foo 实现了__del__. 为了允许在垃圾收集时删除循环,必须将清理从循环中取出。

class Cleanup:
    cleaned = False
    def cleanup(self):
        if self.cleaned:
            return
        print "cleanup"
        self.cleaned = True
    def __del__(self):
        self.cleanup()

class Foo:
    def __init__(self):...
    def start(self):
        self.cleaner = Cleanup()
        atexit.register(Handler(self).cleanup)
    def cleanup(self):
        self.cleaner.cleanup()
    def end(self):
        self.cleanup()

Cleanup 对象没有对 Foo 的引用,这一点很重要。

于 2010-12-28T16:22:56.597 回答
1

你为什么不试试呢?我只花了一分钟的时间检查。

(答案:是的)

但是,您可以简化它。不需要关闭。

class Foo:
   def cleanup(self):
      pass
   def start(self):
      atexit.register(self.cleanup)

并且不清理两次,只需在清理之前检查清理方法是否需要清理。

于 2010-12-28T16:03:19.000 回答