在不使用临时的情况下通过__get__调用__iadd__时发生内存泄漏

col*_*fix 5 python memory-leaks weak-references decorator python-decorators

尝试修改装饰器使用a weakref,我偶然发现了以下行为:

import weakref

class descriptor(object):
    def __get__(self, instance, owner):
        return proxy(instance)

class proxy(object):
    def __init__(self, instance):
        self.instance = instance

    def __iadd__(self, other):
        return self

class A(object):
    descr = descriptor()

def is_leaky(test_fn):
    a = A()
    wr = weakref.ref(a)
    test_fn(a)
    del a
    return wr() is not None

def test1(a):
    tmp = a.descr
    tmp += object()

def test2(a):
    a.descr += object()

print(is_leaky(test1))  # gives False
print(is_leaky(test2))  # gives True!!!
Run Code Online (Sandbox Code Playgroud)

这对我来说似乎很奇怪,因为我希望两种情况都表现得一样.此外,根据我对引用计数和对象生命周期的理解,我确信在这两种情况下都应该释放对象.

我在python2.7和python3.3上测试了它.

这是一个错误还是故意行为?有没有办法让两个调用都有预期的结果(释放有问题的对象)?

我不想使用weakrefin,proxy因为这会破坏绑定方法的正确对象生存期语义:

a = A()
descr = a.descr
del a   # a is kept alive since descr is a bound method to a
descr() # should execute a.descr() as expected
Run Code Online (Sandbox Code Playgroud)

Mar*_*ers 5

这两个代码路径不相同.

就地添加动作对两个操作员,分配目标和添加的项目.在test1这是temp,一个局部变量,并就地除了被翻译成以下几点:

temp = temp.__iadd__(object())
Run Code Online (Sandbox Code Playgroud)

并且由于您返回selftemp引用同一个对象,因此会temp = temp在函数退出后清除该引用.

test2,你复杂的事情,因为现在描述符再次涉及:

a.descr += object() 
Run Code Online (Sandbox Code Playgroud)

变为:

a.descr = A.__dict__['descr'].__get__(a, A).__iadd__(object())
Run Code Online (Sandbox Code Playgroud)

所以你将结果分配给A.__dict__['descr'].__get__(a, A)实例属性a.descr; 描述符没有__set__()方法,也没有参考.

但是,这里是捕获,proxy对象持有对a自身a.descr.instance的引用,是一个引用a!您创建了循环引用.

这个引用使对象保持足够长的时间以通过弱引用显示,但是一旦垃圾收集过程运行并打破这个循环,a无论如何都会消失.

这个故事的道德?不要__iadd__与非数据描述符结合使用; 包括两者__get__ , __set__因为你需要控制分配结果时会发生什么.