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