【问题标题】:Cannot delete matplotlib.animation.FuncAnimation objects无法删除 matplotlib.animation.FuncAnimation 对象
【发布时间】:2015-11-23 15:38:42
【问题描述】:

EDIT/TL;DR: 看起来有一个 matplotlib.backends.backend_qt4.TimerQT 对象持有对我的 FuncAnimation 对象的引用。如何删除它以释放 FuncAnimation 对象?

1 - 一点上下文

我正在尝试对使用 matplotlib 生成的绘图进行动画处理。我使用 matplotlib.animation.FuncAnimation。 这个动画情节包含在一个FigureCanvasQTAgg(matplotlib.backends.backend_qt4agg)中,即。 PyQt4 小部件。

class ViewerWidget(FigureCanvasQTAgg):
    def __init__(self, viewer, parent):
        # viewer is my FuncAnimation, encapsulated in a class
        self._viewer = viewer
        FigureCanvasQTAgg.__init__(self, viewer.figure)

当 GUI 中的配置发生更改时,图形会被清除 (figure.clf()),其子图(轴和线)会被新的替换。

2 - 来自 Viewer 类的源代码(封装 FuncAnimation

这是我的方法Viewer.show(...) 中最相关的部分,它实例化了 FuncAnimation

2.a - 首先,我尝试了:

animation.FuncAnimation(..., blit=True)

当然,实例立即被垃圾回收

2.b - 然后,我将它存储在一个类变量中:

self._anim = animation.FuncAnimation(..., blit=True)

它适用于第一个动画,但是一旦配置更改,我在新动画中都有来自以前动画的伪影

2.c - 所以我手动添加了del

# Delete previous FuncAnimation if any       
if self._anim:
    del self._anim
self._anim = animation.FuncAnimation(..., blit=True)

没有改变

2.d - 经过一些调试,我检查了垃圾收集器:

# DEBUG: check garbage collector
def objects_by_id(id_):
    for obj in gc.get_objects():
        if id(obj) == id_:
            return obj
    self._id.remove(id_)
    return "garbage collected"

# Delete previous FuncAnimation if any       
if self._anim:
    del self._anim

# DEBUG
print "-"*10
for i in self._id.copy():
    print i, objects_by_id(i)
print "-"*10
self._anim = animation.FuncAnimation(self._figure_handler.figure,
                                     update,
                                     init_func=init,
                                     interval=self._update_anim,
                                     blit=True)

# DEBUG: store ids only, to enable object being garbage collected
self._anim_id.add(id(anim))

3次配置更改后,显示:

----------
140488264081616 <matplotlib.animation.FuncAnimation object at 0x7fc5f91360d0>
140488264169104 <matplotlib.animation.FuncAnimation object at 0x7fc5f914b690>
140488145151824 <matplotlib.animation.FuncAnimation object at 0x7fc5f1fca750>
140488262315984 <matplotlib.animation.FuncAnimation object at 0x7fc5f8f86fd0>
----------

因此,它确认 FuncAnimation 没有被垃圾回收

2.e - 最后一次尝试,使用弱引用:

# DEBUG: check garbage collector
def objects_by_id(id_):
    for obj in gc.get_objects():
        if id(obj) == id_:
            return obj
    self._id.remove(id_)
    return "garbage collected"

# Delete previous FuncAnimation if any
if self._anim_ref:
    anim = self._anim_ref()
    del anim


# DEBUG
print "-"*10
for i in self._id.copy():
    print i, objects_by_id(i)
print "-"*10
anim = animation.FuncAnimation(self._figure_handler.figure,
                               update,
                               init_func=init,
                               interval=self._update_anim,
                               blit=True)

self._anim_ref = weakref.ref(anim)

# DEBUG: store ids only, to enable object being garbage collected
self._id.add(id(anim))

这一次,日志令人困惑,我不确定发生了什么。

----------
140141921353872 <built-in method alignment>
----------
----------
140141921353872 <built-in method alignment>
140141920643152 Bbox('array([[ 0.,  0.],\n       [ 1.,  1.]])')
----------
----------
140141921353872 <built-in method alignment>
140141920643152 <viewer.FftPlot object at 0x7f755565e850>
140141903645328 Bbox('array([[ 0.,  0.],\n       [ 1.,  1.]])')
----------
(...)

我的&lt;matplotlib.animation.FuncAnimation object at 0x...&gt;呢?

没有更多以前的动画工件,到目前为止还不错,但是... FuncAnimation 不再能够执行“更新”。只有“初始化”部分。我的猜测是,一旦 Viewer.show(...) 方法返回,FuncAnimation 就会被垃圾回收,因为 anim ids 已经被回收了。

3 - 帮助

我不知道从这里往哪里看。有什么建议吗?

编辑: 我安装了objgraph 来可视化所有对 FuncAnimation 的反向引用,我得到了这个:

            import objgraph, time
            objgraph.show_backrefs([self._anim],
                                   max_depth=5,
                                   filename="/tmp/debug/func_graph_%d.png"
                                   % int(time.time()))

所以,有一个 matplotlib.backends.backend_qt4.TimerQT 仍然持有引用。有什么办法去掉吗?

【问题讨论】:

  • 我有点担心在 2b 中你走错了兔子洞来解决问题所在。如果你调用私有方法anim._stop,它将从定时器回调中重新注册动画对象。
  • 我不确定我是否理解在类变量中存储 FuncAnimation 对象有什么问题,但 anim._stop 成功了!在实例化一个新的 FuncAnimation 之前,我在它上面调用了 _stop(),现在,我没有更多的工件,我检查了垃圾收集器(与 2.d 相同)并且对象实际上是垃圾收集的。好吧,这可能不是调用私有方法的最干净的方法,但现在可以了。如果您从您的评论中做出回答,我会接受。谢谢!

标签: python-2.7 matplotlib garbage-collection pyqt4 blit


【解决方案1】:

要弄清楚这里发生了什么,需要深入了解动画模块和两个回调注册表的工作原理。

当您创建 Animation 对象时,它会在 draw_event 上的 mpl 回调注册表中注册一个回调,以便在创建 Animation 对象后第一次绘制画布后,定时动画会自行设置向上(通过将回调注册到计时器对象)和回调到close_event 上的 mpl 回调注册表中以关闭计时器。

mpl 回调注册表对传入的可调用对象进行了一系列内省,并将绑定的方法重构为对象和相关函数的弱引用。因此,如果你创建了一个动画对象但不保留它的引用,它的引用计数将归零,在 mpl 回调注册表中对其的弱引用将失败,并且动画将永远不会开始。

计时器的工作方式Qt 是您注册一个添加到列表中的可调用对象(我从底部的图表中得到这个),因此它持有对Animation 对象的硬引用,因此删除您在对象中持有的 ref 不足以将 ref 计数驱动为零。在计时器回调的情况下,这可能是一个特性,而不是一个错误。

通过工件,我现在理解的意思是您正在创建第二个 Animation 对象,并且您得到的是它们两个并行运行(我不确定我期望在那里发生什么)。

要停止正在运行的Animation 并将其从计时器的回调列表中删除,请使用私有方法(应该是公共的)_stop,它负责拆除(并且是在close_event 上注册的方法) .

【讨论】:

  • 哦,我刚看到你的个人资料:除其他很酷的东西外,“matplotlib 的联合首席开发人员”。它解释了你的知识!
  • 动画模块与新的自动重绘功能的交互存在一些问题,因此我最近一直在挖掘它以进行调试。
猜你喜欢
  • 1970-01-01
  • 2011-06-23
  • 2021-12-29
  • 2012-11-25
  • 2012-07-25
  • 2021-07-29
  • 2017-08-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多