18

weakref 模块的 Python 2.7 文档是这样说的:

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

弱引用模块的 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 的声明说“实例方法可以被弱引用”;这是否意味着可以合理地预期weakref.ref(an instance method)() 不是None?如果是None,那么“实例方法”不应该列在可以被弱引用的对象类型中吗?

4

3 回答 3

12

Foo.bar每次访问它时都会生成一个新的未绑定方法对象,这是由于有关描述符的一些血腥细节以及方法碰巧在 Python 中实现的方式。

该类不拥有未绑定的方法;它拥有功能。(查看Foo.__dict__['bar']。)这些函数恰好有一个__get__返回一个未绑定方法对象。因为没有其他东西拥有引用,所以一旦你完成创建弱引用,它就会消失。(在 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 甚至没有循环收集,因此它们将永远存在。

于 2013-10-18T07:07:14.523 回答
6

@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 是"Foo instance" 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!)没有指出这一点很奇怪,但这是一个单独的问题。

于 2013-10-18T17:46:34.530 回答
3

虽然我看到对于为什么会这样,有一个公认的答案,但从一个简单的用例情况来看,人们希望一个对象充当绑定方法的弱引用,我相信一个人可能能够偷偷摸摸一个这样的对象。与那里的一些“codier”东西相比,这有点小问题,但它确实有效。

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。

于 2014-06-18T13:54:26.850 回答