问题 对Python类方法的弱引用


用于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的陈述说“实例方法可能被弱引用”;这是不是意味着期望weakref.ref(一个实例方法)()不是None是合理的?如果它是None,那么“实例方法”不应该列在可以弱引用的对象类型中?


6743
2017-10-18 06:57


起源

@Bakuriu - 您对py3k的评论是正确的,但我认为您错误地阅读了关于绑定和未绑定方法不可参考的文档。 - mgilson
@Bakuriu对py3k的陈述相当于“那些可以[弱引用]包括......实例方法的对象”:这肯定说它们是弱的可引用的。 - Oliver
您 能够 weakref实例方法。你的问题是你得到的实例方法没有其他引用,因此它会尽快释放 weakref.ref 回报。 - Eevee


答案:


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

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

绑定方法的工作方式几乎相同:函数 __get__ 返回一个bound-method对象,实际上就是这样 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甚至没有循环收集,所以他们会永远陷入困境。


10
2017-10-18 07:07



实例方法在2和3中以相同的方式工作; 3的文档刚刚删除了未绑定方法的提及,因为那些不再存在 - Eevee
我收回我的原始评论,这个答案没有显示doc语句“实例方法可以被弱引用”和观察到的行为“weak.ref(Foo.bar)()是None == true”是一致的。它实际上显示了它,但有一个微妙的(对我来说,无论如何),使问题蒙上阴影。因此,我将Eevee标记为正确答案,但我在单独的“答案”中对其答案作出澄清(不适合评论)。 - Oliver


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

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

所以@Eevee表明,你 能够 创建一个 -dead对实例方法的弱引用,通过创建对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实例”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!)没有指出它是非常奇怪的,但这是一个单独的问题。


3
2017-10-18 17:46





虽然我看到有一个公认的答案 为什么 这应该是这样,从一个简单的用例情况,其中一个人希望一个对象作为一个弱的参数作为一个绑定的方法,我相信一个人可能能够与一个对象偷偷摸摸。与某些“更健康”的东西相比,这是一种傻瓜,但它有效。

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。


1
2018-06-18 13:54