具有修饰方法和__del__定义的 Python 类不会被垃圾回收:如何解耦修饰的方法



我在使用Python3.2时遇到了问题。如果一个类装饰父类中的函数并且还具有析构函数,则该类的实例永远不会被垃圾回收。

下面是一些说明该问题的示例代码:

def super_simple_decorator(func):
    def f(*args, **kwds):
        return func(*args, **kwds)
    return f
class Parent():
    def foo(self):
        pass
class Child(Parent):
    def __del__(self):
        print('In Child.__del__')
    def __init__(self):
        self.foo = super_simple_decorator(self.foo)
x = Child()
del x
import gc
_ = gc.collect()
print(gc.garbage)

如果你有这样的倾向,你也可以在运行时在装饰器中打补丁,看到同样的东西:

class Garbage():
    def foo(self):
        pass
    def __del__(self):
        print('In Garbage.__del__')
g=Garbage()
g.foo = super_simple_decorator(g.foo)
del g

每种情况下,都有未收集的垃圾,大概是因为在装饰方法中存在对self的绑定引用。

在这一点上,升级到 Python3.4 对我来说并不是一个真正的选择,所以我正在寻找一种方法来让这样的对象被垃圾回收。

导致此问题的不是装饰器。事实上,您将方法存储在它们绑定到的实例上。装饰者只是这里的手段,而不是实际的原因。

方法在 __self__ 中保存对实例的引用,然后通过将方法存储在带有装饰器对象的闭包中来创建循环引用,回到self.foo 上。不要那样做。Python 3.3 及更早版本不会垃圾回收带有 __del__ 方法对象的循环引用。

解开方法包装并存储原始函数:

self.foo = super_simple_decorator(self.foo.__func__)

但是,foo将不再绑定,但只有在类而不是实例上查找时,才会绑定方法。

或者实际上在类级别应用装饰器:

class Child(Parent):
    def __del__(self):
        print('In Child.__del__')
    foo = super_simple_decorator(Parent.foo)

如果两者都不是选项,请使用弱引用来跟踪实例,而不是引用方法,然后根据需要重新绑定:

import weakref
def super_simple_decorator(method):
    instance = weakref.ref(method.__self__)
    func = method.__func__
    def f(*args, **kwds):
        self = instance()  # can return None
        return func(self, *args, **kwds)
    return f

最新更新