diff --git a/doc/users/next_whats_new/2018-12-30-add-cache_frame_data-kwarg-to-FuncAnimation.rst b/doc/users/next_whats_new/2018-12-30-add-cache_frame_data-kwarg-to-FuncAnimation.rst new file mode 100644 index 000000000000..abba9770f66c --- /dev/null +++ b/doc/users/next_whats_new/2018-12-30-add-cache_frame_data-kwarg-to-FuncAnimation.rst @@ -0,0 +1,10 @@ +Add ``cache_frame_data`` keyword-only argument into ``matplotlib.animation.FuncAnimation`` +------------------------------------------------------------------------------------------ + +| ``matplotlib.animation.FuncAnimation`` has been caching frame data by default. + +| However, this caching is not ideal in certain cases. +| e.g. When ``FuncAnimation`` needs to be only drawn(not saved) interactively and memory required by frame data is quite large. + +| By adding ``cache_frame_data`` keyword-only argument, users can disable this caching now if necessary. +| Thereby, this new argument provides a fix for issue #8528. diff --git a/lib/matplotlib/animation.py b/lib/matplotlib/animation.py index aaf25d573df4..5ab67627576e 100644 --- a/lib/matplotlib/animation.py +++ b/lib/matplotlib/animation.py @@ -1598,10 +1598,14 @@ def init_func() -> iterable_of_artists blitting any animated artists will be drawn according to their zorder. However, they will be drawn on top of any previous artists, regardless of their zorder. Defaults to *False*. + + cache_frame_data : bool, optional + Controls whether frame data is cached. Defaults to *True*. + Disabling cache might be helpful when frames contain large objects. """ def __init__(self, fig, func, frames=None, init_func=None, fargs=None, - save_count=None, **kwargs): + save_count=None, *, cache_frame_data=True, **kwargs): if fargs: self._args = fargs else: @@ -1649,6 +1653,8 @@ def __init__(self, fig, func, frames=None, init_func=None, fargs=None, # for a single frame from init, which is not what we want. self._save_seq = [] + self._cache_frame_data = cache_frame_data + def new_frame_seq(self): # Use the generating function to generate a new frame sequence return self._iter_gen() @@ -1703,8 +1709,9 @@ def _init_draw(self): self._save_seq = [] def _draw_frame(self, framedata): - # Save the data for potential saving of movies. - self._save_seq.append(framedata) + if self._cache_frame_data: + # Save the data for potential saving of movies. + self._save_seq.append(framedata) # Make sure to respect save_count (keep only the last save_count # around) diff --git a/lib/matplotlib/tests/test_animation.py b/lib/matplotlib/tests/test_animation.py index a99c673fa1d2..c14b70a38725 100644 --- a/lib/matplotlib/tests/test_animation.py +++ b/lib/matplotlib/tests/test_animation.py @@ -2,6 +2,7 @@ from pathlib import Path import subprocess import sys +import weakref import numpy as np from pathlib import Path @@ -258,3 +259,51 @@ def test_failing_ffmpeg(tmpdir, monkeypatch): make_animation().save("test.mpeg") finally: animation.writers.reset_available_writers() + + +@pytest.mark.parametrize("cache_frame_data, weakref_assertion_fn", [ + pytest.param( + False, lambda ref: ref is None, id='cache_frame_data_is_disabled'), + pytest.param( + True, lambda ref: ref is not None, id='cache_frame_data_is_enabled'), +]) +def test_funcanimation_holding_frames(cache_frame_data, weakref_assertion_fn): + fig, ax = plt.subplots() + line, = ax.plot([], []) + + class Frame(dict): + # this subclassing enables to use weakref.ref() + pass + + def init(): + line.set_data([], []) + return line, + + def animate(frame): + line.set_data(frame['x'], frame['y']) + return line, + + frames_generated = [] + + def frames_generator(): + for _ in range(5): + x = np.linspace(0, 10, 100) + y = np.random.rand(100) + + frame = Frame(x=x, y=y) + + # collect weak references to frames + # to validate their references later + frames_generated.append(weakref.ref(frame)) + + yield frame + + anim = animation.FuncAnimation(fig, animate, init_func=init, + frames=frames_generator, + cache_frame_data=cache_frame_data) + + writer = NullMovieWriter() + anim.save('unused.null', writer=writer) + assert len(frames_generated) == 5 + for f in frames_generated: + assert weakref_assertion_fn(f())