对Python类方法的弱引用

Oli*_*ver 15 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
Run Code Online (Sandbox Code Playgroud)

不是我根据文档所期望的.

类似地,在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
Run Code Online (Sandbox Code Playgroud)

再次不是我根据文档所期望的.

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

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

Eev*_*vee 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
Run Code Online (Sandbox Code Playgroud)

这一切似乎都值得怀疑,但:)

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


Oli*_*ver 6

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

Python文档指出实例方法(py3k)和未绑定方法(py2.4 +)可以被弱引用。您可能希望(像我一样,天真地)weakref.ref(foo.bar)()不会是None,但是它是None,会使弱引用成为“到达时死”(DOA)。这导致了我的问题,如果对实例方法的weakref是DOA,为什么文档会说您可以对方法进行弱引用?

因此,如@Eevee所示,您可以通过创建对方法对象的强引用来创建对实例方法的死弱引用:

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

微妙的是(无论如何对我而言)是每次您使用Foo.bar时都会创建一个新的实例方法对象,因此即使在运行上述代码之后,以下操作也会失败:

wr = weakref.ref(foo.bar)
assert wr() is not None # fails
Run Code Online (Sandbox Code Playgroud)

因为foo.bar是foo的“ bar”方法的实例,是foo的“ bar”方法,不同于m,并且对该新实例没有强引用,所以即使您创建了强引用,它也会立即被gc引用早一点(不是相同的强引用)。要清楚一点

>>> 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
Run Code Online (Sandbox Code Playgroud)

这使许多人感到惊讶,因为没有人期望访问实例成员可以创建任何事物的新实例。例如,如果foo.bla是foo的数据成员,则在代码中使用foo.bla不会创建foo.bla引用的对象的新实例。现在,如果bla是“函数”,则foo.bla确实会创建一个表示绑定函数的“实例方法”类型的新实例。

为什么弱引用文档(自python 2.4起)没有指出这一点很奇怪,但这是一个单独的问题。

  • 请注意,如果你做了 `wr = weakref.WeakMethod(foo.bar)` 那么上面的 quirk 就会得到处理。 (2认同)