请参阅以下两个函数,第一个函数返回一个函数闭包,第二个函数返回一个"类闭包"。 objects
用于跟踪创建的对象。在这两种情况下,闭包都会捕获MyObject
的实例。
import weakref
class MyObject(object):
pass
def leak1():
obj = MyObject()
objects[id(obj)] = weakref.ref(obj)
def inner():
return obj
return inner
def leak2():
obj = MyObject()
objects[id(obj)] = weakref.ref(obj)
class Inner(object):
__slots__ = () # edit
def __call__(self):
return obj
#def __del__(self):
# nonlocal obj
# del obj
return Inner()
def print_all_objects(s):
for id, ref in objects.items():
print(s, id, ref())
for leak in (leak1, leak2):
print(leak.__name__)
objects = {}
a = leak()
print_all_objects(1)
del a
print_all_objects(2)
如果你运行它,你会得到以下输出:
leak1
(1, 54150256L, <__main__.MyObject object at 0x00000000033A4470>)
(2, 54150256L, None)
leak2
(1, 54150256L, <__main__.MyObject object at 0x00000000033A4470>)
(2, 54150256L, <__main__.MyObject object at 0x00000000033A4470>)
这意味着在第一种情况下删除函数闭包后删除obj
(这是我的预期)。
但在第二种情况下,永远不会删除obj
。这可以使用nonlocal
和__del__
在Python 3中修复,但在Python 2.7中则不行,因为nonlocal
不存在。
所以我的问题是:为什么在类的情况下不删除捕获的变量;并且:如何使用weakref
答案 0 :(得分:3)
您无需做任何事情。
CPython使用引用计数和垃圾收集器的组合来处理不需要的对象。在第一种情况下,使用del a
删除闭包会将泄漏对象的引用计数减少到0并立即处理。在第二种情况下,Inner
类,其__call__
方法和obj
之间存在参考周期。此引用循环可防止引用计数降为0,因此不会立即删除闭包。但是一旦垃圾收集器开始其下一个收集周期,关闭将处理掉。
如果要立即删除闭包,可以使用gc.collect()
手动触发垃圾回收:
import gc
for leak in (leak1, leak2):
print(leak.__name__)
objects = {}
a = leak()
print_all_objects(1)
del a
gc.collect() # <- add this
print_all_objects(2)
输出:
leak1
1 140591616726800 <__main__.MyObject object at 0x7fde095f9710>
2 140591616726800 None
leak2
1 140591619339880 <__main__.MyObject object at 0x7fde09877668>
2 140591619339880 None