python 2.7 - No se pueden eliminar los objetos matplotlib.animation.FuncAnimation
python-2.7 garbage-collection (1)
Para resolver lo que está pasando aquí, hay que ir al grano de cómo funcionan el módulo de animación y los dos registros de devolución de llamada.
Cuando crea el objeto Animation
registra una devolución de llamada en el registro de devolución de llamada mpl en draw_event
para que después de la primera vez que se draw_event
el lienzo después de crear el objeto Animation
la animación temporizada lo configure (registrando una devolución de llamada en un temporizador objeto) y una devolución de llamada en el registro de devolución de llamada mpl en close_event
para romper el temporizador.
El registro de devolución de llamada de MPL hace un montón de introspección de los callables que entran y reconstruye los métodos vinculados en un punto débil para el objeto y la función relevante. Por lo tanto, si usted crea un objeto de animación pero no lo refinancia, su refcount se irá a cero, el punto débil en el registro de devolución de llamada de mpl fallará, y la animación nunca comenzará.
La forma en que funciona el temporizador Qt
es que registras un llamativo que se agrega a una lista (obtengo esto del diagrama en la parte inferior) por lo que contiene una referencia dura al objeto Animation
, eliminando así la referencia que tienes en su objeto no es suficiente para llevar el conteo de ref a cero. En el caso de las devoluciones de llamada de los temporizadores, esta es probablemente una característica, no un error.
Por artefactos, ahora entiendo que significa que estás creando un segundo objeto de Animation
y lo que obtienes es que ambos se ejecutan en paralelo (lo cual no estoy seguro de lo que espero que suceda allí).
Para detener una Animation
ejecución y eliminarla de la lista de devolución de llamada del temporizador, utilice el método privado (que debería ser público) _stop
que es el responsable del derribo (y es el método registrado en close_event
).
EDIT / TL; DR: Parece que hay un objeto matplotlib.backends.backend_qt4.TimerQT
que contiene una referencia a mi objeto FuncAnimation. ¿Cómo puedo eliminarlo para liberar el objeto FuncAnimation?
1 - Un pequeño contexto
Intento animar una trama generada con matplotlib. Yo uso matplotlib.animation.FuncAnimation. Este gráfico animado está contenido en un FigureCanvasQTAgg (matplotlib.backends.backend_qt4agg), es decir. un widget 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)
Cuando se produce un cambio de configuración en la GUI, la Figura se borra ( figure.clf()
) y sus subtramas (ejes y líneas) se reemplazan por nuevas.
2 - Código fuente del Viewer
clases (que encapsula FuncAnimation
)
Esta es la parte más relevante de mi método Viewer.show(...)
, que instancia la FuncAnimation
2.a - Primero, probé:
animation.FuncAnimation(..., blit=True)
Por supuesto, la instancia fue recogida de basura inmediatamente
2.b - Entonces, lo almacené en una variable de clase:
self._anim = animation.FuncAnimation(..., blit=True)
Funcionó para la primera animación, pero tan pronto como cambió la configuración, tuve artefactos de animaciones anteriores en todos los nuevos
2.c - Así que agregué manualmente un del
:
# Delete previous FuncAnimation if any
if self._anim:
del self._anim
self._anim = animation.FuncAnimation(..., blit=True)
nada ha cambiado
2.d - Después de algunas depuraciones, revisé el recolector de basura:
# 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))
Después de 3 cambios de configuración, mostró:
----------
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>
----------
Entonces, confirmó que ninguna de las FuncAnimation fue recogida de basura
2.e - Última prueba, con 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))
Esta vez, los registros son confusos, no estoy seguro de lo que está pasando.
----------
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.]])'')
----------
(...)
¿Dónde está mi <matplotlib.animation.FuncAnimation object at 0x...>
?
No hubo más artefactos de animación anteriores, hasta ahora todo bien, pero ... FuncAnimation ya no puede ejecutar la "actualización". Solo la parte "init". Mi suposición es que FuncAnimation es basura recolectada tan pronto como el método Viewer.show(...)
retorna, las identidades de los animales ya han sido recicladas.
3 - Ayuda
No sé dónde mirar desde aquí. ¿Cualquier sugerencia?
EDIT: instalé objgraph para visualizar todas las referencias anteriores a FuncAnimation, obtuve esto:
import objgraph, time
objgraph.show_backrefs([self._anim],
max_depth=5,
filename="/tmp/debug/func_graph_%d.png"
% int(time.time()))
Entonces, hay un matplotlib.backends.backend_qt4.TimerQT
que aún contiene una referencia. ¿Alguna forma de eliminarlo?