尝试修改装饰者而不是以使用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上测试了它。
这是一个错误还是故意行为? 有没有办法让两个调用都有预期的结果(释放有问题的对象)?
我不想在weakref
中使用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
答案 0 :(得分:5)
两个代码路径不等效。
就地添加动作两个运算符,分配目标和添加的项目。在test1
temp
中,本地变量和就地添加转换为以下内容:
temp = temp.__iadd__(object())
并且由于您返回self
并且temp
引用了同一个对象,因此该变为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__
,因为您需要控制在分配结果时会发生什么。