Python:闭包与类
我需要为一个类注册一个 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 个回答
因为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的引用。
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。没关系,我还是把这个回答留在这里,希望能帮助到其他人。
把一个注册表变成全局的注册表,并创建一个可以在这个注册表里调用函数的功能,同时在需要的时候把它们从注册表中移除。
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)