Python:闭包与类

6 投票
5 回答
4901 浏览
提问于 2025-04-16 09:08

我需要为一个类注册一个 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 的内容

5 个回答

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的情况下被释放的对象;在最后一个引用消失时进行清理
  • 那些存在循环引用的对象;在程序结束时进行清理
  • 那些一直被保留的对象;在程序结束时进行清理

需要注意的是,清理处理程序必须持有对象的弱引用,否则会导致对象一直存在。

编辑:涉及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()

重要的是,清理对象不能有指向Foo的引用。

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。没关系,我还是把这个回答留在这里,希望能帮助到其他人。

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)

撰写回答