5

尝试修改装饰器以使用 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!!!

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

我已经在 python2.7 和 python3.3 上测试过它。

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

我不想使用weakrefinproxy因为这会破坏绑定方法的正确对象生命周期语义:

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
4

1 回答 1

5

这两个代码路径不等效。

就地添加作用于两个运算符,分配目标和添加的项目。test1也就是说,一个局部变量,并且temp就地添加转换为以下内容:

temp = temp.__iadd__(object())

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

test2中,您的问题很复杂,因为现在描述符再次涉及:

a.descr += object() 

变成:

a.descr = A.__dict__['descr'].__get__(a, A).__iadd__(object())

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

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

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

这个故事的寓意?不要__iadd__与非数据描述符结合使用;包括两者__get__ __set__因为您需要控制分配回结果时发生的情况。

于 2013-08-17T10:12:59.047 回答