对Python类方法的弱引用

时间:2013-10-18 06:57:35

标签: python python-2.7 python-3.x

用于weakref模块的Python 2.7文档说:

  

并非所有对象都可以被弱引用;那些可以的物体   包括类实例,用Python编写的函数(但不是在C中),   方法(绑定和未绑定),...

而对于weakref模块的Python 3.3文档说:

  

并非所有对象都可以被弱引用;那些可以的物体   包括类实例,用Python编写的函数(但不是在C中),   实例方法,......

对我来说,这些表明对绑定方法的弱化(在所有版本的Python 2.7 - 3.3中)应该是好的,并且对于未绑定的方法的弱化应该在Python 2.7中很好。

然而在Python 2.7中,为方法创建一个弱参数(绑定或未绑定)会导致一个死弱点:

>>> def isDead(wr): print 'dead!'
...
>>> class Foo: 
...    def bar(self): pass
...
>>> wr=weakref.ref(Foo.bar, isDead)
dead!
>>> wr() is None
True
>>> foo=Foo()
>>> wr=weakref.ref(foo.bar, isDead)
dead!
>>> wr() is None
True

不是我根据文档所期望的那样。

类似地,在Python 3.3中,绑定方法的弱参数在创建时死亡:

>>> wr=weakref.ref(Foo.bar, isDead)
>>> wr() is None
False
>>> foo=Foo()
>>> wr=weakref.ref(foo.bar, isDead)
dead!
>>> wr() is None
True

再次不是我根据文档所期望的那样。

由于这个措辞自2.7以来一直存在,它肯定不是一个疏忽。任何人都可以解释这些陈述和观察到的行为实际上是的矛盾吗?

编辑/澄清:换句话说,3.3的陈述说"实例方法可以被弱引用&#34 ;;并不意味着期望weakref.ref(实例方法)()不是None是合理的吗?如果没有,那么"实例方法"不应该列在可以弱引用的对象类型中?

3 个答案:

答案 0 :(得分:11)

每次访问它时,

Foo.bar都会生成一个新的未绑定方法对象,因为有一些关于描述符的详细信息以及如何在Python中实现这些方法。

该类不拥有未绑定的方法;它拥有功能。 (查看Foo.__dict__['bar']。)这些函数碰巧有一个__get__,它返回一个unbound-method对象。因为没有别的东西可以引用它,所以一旦你创建了weakref,它就会消失。 (在Python 3中,相当不必要的额外层消失了,“未绑定方法”只是底层函数。)

绑定方法的工作方式大致相同:函数__get__返回绑定方法对象,实际上只是partial(function, self)。你每次都会得到一个新的,所以你会看到同样的现象。

您可以存储方法对象并保留对 的引用,当然:

>>> def is_dead(wr): print "blech"
... 
>>> class Foo(object):
...     def bar(self): pass
... 
>>> method = Foo.bar
>>> wr = weakref.ref(method, is_dead)
>>> 1 + 1
2
>>> method = None
blech

这一切看起来都是可疑的,但是:)

请注意,如果Python 没有在每个属性访问上吐出一个新的方法实例,那就意味着类引用它们的方法和方法引用它们的类。对整个程序中的每个单个实例上的每个单独方法进行这样的循环会使垃圾收集方式更加昂贵 - 而在2.1之前,Python甚至没有循环收集,所以它们会陷入困境永远。

答案 1 :(得分:5)

@ Eevee的回答是正确的,但有一个重要的微妙之处。

Python文档声明实例方法(py3k)和un / bound方法(py2.4 +)可以被弱引用。你会(天真地,像我一样)期望weakref.ref(foo.bar)()因此是非 - 无,但它是无,使弱的参考“在抵达时死亡”(DOA)。这导致了我的问题,如果实例方法的弱参数是DOA,为什么文档说你可以弱一个方法?

正如@Eevee所示,你可以创建一个 - 对实例方法的弱引用,方法是创建一个对你赋予的方法对象的强引用weakref:

m = foo.bar # creates a *new* instance method "Foo.bar" and strong refs it
wr = weakref.ref(m)
assert wr() is not None # success

微妙(对我来说,无论如何)是新的实例方法对象是在每次时使用Foo.bar创建的,所以即使在上面的代码运行之后也是如此,以下将失败:

wr = weakref.ref(foo.bar)
assert wr() is not None # fails

因为foo.bar是 new 实例的“Foo实例”foo的“bar”方法,与m不同,并且没有强烈的引用这个新实例,所以它立即是gc' d,即使你之前已经创建了一个强引用(它不是同一个强引用)。要清楚,

>>> d1 = foo.bla # assume bla is a data member
>>> d2 = foo.bla # assume bla is a data member
>>> d1 is d2
True # which is what you expect
>>> m1 = foo.bar # assume bar is an instance method
>>> m2 = foo.bar
>>> m1 is m2
False  # !!! counter-intuitive

这让很多人感到意外,因为没有人希望访问实例成员来创建任何新的实例。例如,如果foo.bla是foo的数据成员,那么在代码中使用foo.bla不会创建foo.bla引用的对象的新实例。现在,如果bla是一个“函数”,foo.bla会创建一个表示绑定函数的“实例方法”类型的新实例。

为什么weakref docs(因为python 2.4!)没有指出它是非常奇怪的,但这是一个单独的问题。

答案 2 :(得分:1)

虽然我看到为什么这个应该是这样的答案,但是从一个简单的用例情况开始,人们希望一个对象充当弱点。绑定方法,我相信一个人可能能够用一个物体偷偷摸摸。与一些“抄写员”相比,它是一种傻瓜。那里的东西,但它的确有效。

from weakref import proxy

class WeakMethod(object):
    """A callable object. Takes one argument to init: 'object.method'.
    Once created, call this object -- MyWeakMethod() -- 
    and pass args/kwargs as you normally would.
    """
    def __init__(self, object_dot_method):
        self.target = proxy(object_dot_method.__self__)
        self.method = proxy(object_dot_method.__func__)
        ###Older versions of Python can use 'im_self' and 'im_func' in place of '__self__' and '__func__' respectively

    def __call__(self, *args, **kwargs):
        """Call the method with args and kwargs as needed."""
        return self.method(self.target, *args, **kwargs)

作为易用性的一个例子:

class A(object):
    def __init__(self, name):
        self.name = name
    def foo(self):
        return "My name is {}".format(self.name)

>>> Stick = A("Stick")
>>> WeakFoo = WeakMethod(Stick.foo)
>>> WeakFoo()
'My name is Stick'
>>> Stick.name = "Dave"
>>> WeakFoo()
'My name is Dave'

请注意,恶意欺骗会导致这种情况爆发,因此根据您喜欢的方式,这可能不是最佳解决方案。

>>> A.foo = lambda self: "My eyes, aww my eyes! {}".format(self.name)
>>> Stick.foo()
'My eyes, aww my eyes! Dave'
>>> WeakFoo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in __call__
ReferenceError: weakly-referenced object no longer exists
>>>

如果您要在运行中替换方法,则可能需要使用getattr(weakref.proxy(object), 'name_of_attribute_as_string')方法。 getattr是一个相当快速的查找,所以这不是世界上最糟糕的事情,但取决于你正在做什么,YMMV。