Cannot delete matplotlib.animation.FuncAnimation objects

末鹿安然 提交于 2019-12-09 22:59:55

问题


EDIT/TL;DR: It looks like there is a matplotlib.backends.backend_qt4.TimerQT object that hold a reference to my FuncAnimation object. How can I remove it to free the FuncAnimation object?

1 - A little context

I'm trying to animate a plot generated with matplotlib. I use matplotlib.animation.FuncAnimation. This animated plot is contained in a FigureCanvasQTAgg (matplotlib.backends.backend_qt4agg), ie. a PyQt4 widget.

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

When a change of configuration occure in the GUI, the Figure is cleared (figure.clf()) and its subplots (axes and lines) replaced by new ones.

2 - Source code from Class Viewer (encapsulating FuncAnimation)

This is the most relevant part of my method Viewer.show(...), that instanciate the FuncAnimation

2.a - First, I tried:

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

Of course, the instance was garbage collected immediatly

2.b - Then, I stored it in a class variable:

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

It worked for the first animation, but as soon as the configuration changed, I had artifacts from previous animations all over the new ones

2.c - So I manually added a del:

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

Nothing changed

2.d - After some debugging, I checked the garbage collector:

# 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))

After 3 configuration changes, it showed:

----------
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>
----------

So, it confirmed that none of the FuncAnimation were garbage collected

2.e - Last try, with weakref:

# 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))

This time, logs where confusing, I'm not sure what's going on.

----------
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.]])')
----------
(...)

Where are my <matplotlib.animation.FuncAnimation object at 0x...>?

There were no more previous animation artifacts, so far so good, but... FuncAnimation is no longer able to execute the "update". Only the "init" part. My guess is the FuncAnimation is garbage collected as soon as the method Viewer.show(...) returns, sinces anim ids are already recycled.

3 - Help

I don't know where to look from here. Any suggestion?

EDIT: I installed objgraph to visualize all back references to FuncAnimation, I got this:

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

So, there is a matplotlib.backends.backend_qt4.TimerQT that still hold a reference. Any way to remove it?


回答1:


To sort out what is going on here involves going down into the guts of how the animation module and two callback registries work.

When you create the Animation object it registers a callback into the mpl callback registry on the draw_event so that after the first time that the canvas is drawn after the Animation object is created the timed animation sets it self up (by registering a callback into a timer object) and a callback into the mpl callback registry on the close_event to tear the timer down.

The mpl callback registry does a bunch of introspection of the callables that come in and reconstructs bound methods into a weakref to the object an the relevant function. Thus, if you you create an animation object but don't keep a ref to it, it's refcount will go to zero, the weakref to it in the mpl callback registry will fail, and the animation will never start.

The way that the timer works it Qt is that you register a callable which is added to a list (I am getting this from your diagram at the bottom) so it is holding a hard reference to the Animation object, thus removing the ref you hold in your object is not enough to drive the ref count to zero. In the case of timers callbacks this is probably a feature, not a bug.

By artifacts, I now understand to mean you are creating a second Animation object and what you get is both of them running in parallel (which I am not sure what I expect to happen there).

To stop a running Animation and remove it from the timer's callback list use the private method (which should be public) _stop which is what responsible for the tear down (and is the method registered on close_event).



来源:https://stackoverflow.com/questions/32280140/cannot-delete-matplotlib-animation-funcanimation-objects

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!