使用装饰方法和__del__定义的Python类不会收集垃圾:如何解开装饰方法?

Tre*_*ley 2 python garbage-collection decorator python-decorators

我遇到了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)
Run Code Online (Sandbox Code Playgroud)

如果你是如此倾向,你也可以在运行时在装饰器中进行猴子补丁并看到同样的事情:

class Garbage():
    def foo(self):
        pass
    def __del__(self):
        print('In Garbage.__del__')

g=Garbage()
g.foo = super_simple_decorator(g.foo)
del g
Run Code Online (Sandbox Code Playgroud)

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

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

Mar*_*ers 5

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

方法保存对实例的引用__self__,然后通过将该方法存储在带有装饰器对象的闭包中,然后创建一个循环引用self.foo.不要那样做.Python 3.3及之前的版本不会使用带有__del__方法的对象来回收循环引用.

打开方法并存储原始函数:

self.foo = super_simple_decorator(self.foo.__func__)
Run Code Online (Sandbox Code Playgroud)

foo 然而,将不再绑定方法,只有在查询类而不是实例时才绑定方法.

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

class Child(Parent):
    def __del__(self):
        print('In Child.__del__')
    foo = super_simple_decorator(Parent.foo)
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)