8

我今天写了这样的东西(与 mpl_connect 文档不同:

class Foo(object):
    def __init__(self): print 'init Foo', self
    def __del__(self): print 'del Foo', self
    def callback(self, event=None): print 'Foo.callback', self, event


from pylab import *
fig = figure()
plot(randn(10))
cid = fig.canvas.mpl_connect('button_press_event', Foo().callback)
show()

这看起来很合理,但它不起作用——就好像 matplotlib 忘记了我给它的函数。如果Foo().callback我不通过它,而是通过它lambda e: Foo().callback(e),它可以工作。同样,如果我说x = Foo()然后通过它x.callback,它会起作用。

我的假设是,创建的未命名 Foo 实例Foo()在该行之后立即被销毁mpl_connect——具有Foo.callback引用的 matplotlib 不会保持Foo活动状态。那是对的吗?

在我遇到的非玩具代码中,解决方案x = Foo()不起作用,大概是因为在那种情况下show()在其他地方所以x超出了范围。

更一般地说,Foo().callback是一个<bound method Foo.callback of <__main__.Foo object at 0x03B37890>>。我的主要惊讶是,绑定方法似乎实际上并没有保留对对象的引用。那是对的吗?

4

2 回答 2

6

是的,绑定方法引用对象 - 对象是绑定方法对象.im_self属性的值。

所以我想知道matplotlib'smpl_connect()是否记得增加传递给它的参数的引用计数。如果不是(这是一个常见错误),那么在返回时没有什么可以让匿名者保持Foo().callback活动状态mpl_connect()

如果您可以轻松访问源代码,请查看mpl_connect()实现?你想看到 C 代码在做Py_INCREF();-)

编辑这看起来很相关,来自此处的文档

画布仅保留对回调的弱引用。因此,如果回调是类实例的方法,则需要保留对该实例的引用。否则,实例将被垃圾回收,回调将消失。

所以这是你的错——哈哈 ;-)

于 2013-10-24T03:15:48.070 回答
1

这是来自的理由matplotlib.cbook.CallbackRegistry.__doc__

在实践中,当不再需要所有回调时,应始终断开所有回调,以避免悬空引用(从而导致内存泄漏)。但是,matplotlib 中的实际代码很少这样做,并且由于其设计,放置这种代码相当困难。为了解决这个问题并防止此类内存泄漏,我们只存储对绑定方法的弱引用,因此当目标对象需要终止时,CallbackRegistry 不会使其保持活动状态。Python stdlibweakref 模块不能直接创建对绑定方法的弱引用,所以我们需要创建一个代理对象来处理对绑定方法(或常规自由函数)的弱引用。Peter Parente 在他的 "Mindtrove" blog <http://mindtrove.info/articles/python-weak-references/>_ 上分享了这项技术。

很遗憾,没有官方的方法可以绕过这种行为。

这是一个绕过它的方法,它很脏,但对于非生产测试/诊断代码可能没问题:附上Foo图:

fig._dont_forget_this = Foo()
cid = fig.canvas.mpl_connect('button_press_event', fig._dont_forget_this.callback)

这仍然留下了为什么lambda e: Foo().callback(e)有效的问题。它显然Foo在每次调用时都会产生一个新的,但为什么 lambda 不被垃圾收集呢?它起作用的事实只是未定义行为的一种情况吗?

于 2013-10-24T12:14:54.523 回答