如何删除闭包中捕获的变量 class

How to delete variable captured in closure class

看下面两个函数,第一个returns一个函数闭包,第二个"class closure"。 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 不存在。

所以我的问题是:为什么捕获的变量在class的情况下没有被删除;并且:如何在 Python 2.7 中删除它而不使用一些使用 weakref 的奇怪跟踪机制?

您无需执行任何操作。

CPython 结合使用引用计数和垃圾收集器来处理不需要的对象。在第一种情况下,删除带有 del a 的闭包会将泄漏对象的引用计数减少到 0,并立即将其处理掉。在第二种情况下,Inner class、它的 __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