5.4.18 · D5Scientific Computing (Python)

Question bank — Animation — FuncAnimation

1,535 words7 min readBack to topic

Before we start, three words we will lean on — defined in plain language so nothing is assumed:


True or false — justify

FuncAnimation builds a new figure for every frame.
False — it reuses one persistent figure and only updates the artists on it; rebuilding per frame is the slow, flickery thing we are avoiding.
interval controls the playback speed of a saved .mp4.
False — interval only paces the live on-screen timer; when saving you pass fps to save, and matplotlib spaces frames at 1/fps regardless of interval.
With blit=False you still must return the artists from func.
False — the return value is only consumed by the blitting machinery; with blit=False matplotlib redraws everything, so a missing return is harmless (though still good habit).
frames=200 and frames=range(200) behave identically.
True — an integer N is shorthand for iterating 0..N-1, exactly what range(200) yields; each value is handed to func as frame.
If you forget anim = ... and just write FuncAnimation(...), it will still animate because matplotlib keeps its own reference.
False — matplotlib does not hold a strong reference; with no variable pointing at it the object is garbage-collected, its timer dies, and you get a frozen/blank window.
init_func is optional.
True — if omitted, matplotlib calls func once to seed the first frame; you only need init_func to explicitly draw a clean, artist-free background for blitting.
Calling plt.plot(...) inside func produces the same result as line.set_data(...).
False — plt.plot adds a new artist every frame, so old curves pile up, memory grows, and blit can't tell what changed; set_data mutates the one existing artist.
return line and return line, do the same thing.
False — return line returns a single Line2D (not iterable of artists as blit expects); return line, returns a 1-tuple containing the artist, which is the required iterable.
FPS and interval are inversely related.
True — , so a larger interval (more ms between frames) means fewer frames per second, i.e. slower playback.

Spot the error

line = ax.plot([], []) then later line.set_data(x, y) — what breaks?
ax.plot returns a list, so line is a list, not a Line2D; the fix is the unpacking comma: line, = ax.plot([], []).
Inside update, the author writes dot.set_data(x[i], np.sin(x[i])) for a single moving point — why can it fail?
set_data wants sequences, not scalars; pass lists: dot.set_data([x[i]], [np.sin(x[i])]), otherwise you may get shape/plotting errors.
update animates a line and a dot but only does return line, — symptom?
The dot appears frozen, because blit redraws exactly what you return; you must return line, dot so both moved artists get repainted.
FuncAnimation(fig, update, frames=my_generator, blit=True) then anim.save(...) raises about unknown length — fix?
A generator has no known length, so saving can't tell how many frames to grab; add ==save_count=N== to cap the captured frames.
ax.set_xlim / ax.set_ylim are called inside update every frame with blit=True — why is that a mistake?
Blit caches the background (including axis limits) once; changing limits per frame invalidates that cached background, so the moving artist is pasted onto a stale, wrong-scaled photo.
The author does def update(): ... (no parameter) — what happens?
FuncAnimation always passes the current frame value, so a zero-argument update raises a TypeError about too many arguments.
return line, is written but the function created a fresh line inside update with ax.plot.
Each frame spawns a new artist and returns that; the old ones stay on screen accumulating — you must define line once outside and only set_data inside.

Why questions

Why must the moving artist be created outside func?
So there is exactly one artist to mutate each frame; creating it inside would add a new artist per frame, breaking blit and leaking memory.
Why does blitting make animation faster?
It avoids repainting expensive static parts (ticks, grid, labels) every frame — the background is cached as a bitmap once and only the returned artists are re-rendered and copied on top.
Why do we set axis limits once, before animating, rather than letting them autoscale?
Autoscaling redraws/reshapes the axes, which defeats the cached background and can make the view jump; fixed limits keep the blit background valid and the frame stable.
Why does FuncAnimation own the timer instead of you writing a for loop with plt.show()?
A manual show() blocks until the window closes and rebuilds the figure each pass; the built-in timer fires inside the GUI event loop, keeping the window responsive while updating one figure.
Why is frames allowed to be a generator rather than only an integer?
Some animations need custom per-frame values (irregular time steps, streamed data), not just 0..N-1; a generator yields whatever value should become frame each call.
Why does the trailing comma appear in both line, = ax.plot(...) and return line,?
The first comma unpacks the single-element list plot returns; the second packs a 1-tuple so blit receives an iterable of artists, even though there is only one.
Why can keeping anim in a short-lived local scope still cause a freeze?
When that scope ends (e.g. a function returns), the reference is dropped and the object is collected mid-play; keep anim alive at module/persistent scope.
Why does saving to .mp4 need ffmpeg while showing on screen does not?
The live window uses the GUI backend to paint pixels, but encoding a video file requires an external encoder (ffmpeg) that matplotlib shells out to.

Edge cases

What frame values does func receive when frames=0?
None — there are no frames 0..-1, so func is never driven and you see only the init_func/first draw (effectively a static figure).
With repeat=True and a generator for frames, what subtle problem appears on the second loop?
A plain generator is exhausted after one pass and cannot restart; you must pass a callable that returns a fresh generator (or an integer/iterable) so repeat can re-run it.
If interval is set to 0, does the animation run infinitely fast?
No — the timer still yields to the event loop, so you get "as fast as the backend allows," bounded by render time, not a true zero-delay infinite rate.
What does the very first displayed image look like if init_func returns empty data?
A clean blank background with the artist present but drawing nothing (empty set_data([], [])), which is exactly the "state before frame 0."
For N=200 frames at interval=20 ms, how long is one on-screen pass?
s, since time-per-frame × frame-count = total wall-clock time.
If you set save(fps=50) but the animation was built with interval=20, what governs the saved video's speed?
The saved fps=50 wins for the file; interval is ignored during saving, so both happen to give 50 FPS here — but change interval and the file is unaffected.
What happens on the last generator value if save_count is larger than the number of yields?
The generator stops early, so fewer frames than save_count are saved; save_count is only an upper cap, not a guarantee of that many frames.
Degenerate case: func returns nothing (return None) with blit=True.
Blit has no artists to repaint, so the moving object appears frozen even though set_data ran — always return the changed artists.

Connections

  • Animation — FuncAnimation — the parent note these traps drill.
  • Artists in Matplotlib — why one artist is mutated, not recreated.
  • Generators in Python — the frames/save_count/repeat subtleties.
  • Event Loops / GUI backends — the timer that keeps the window alive.
  • Matplotlib Figure and Axes — the persistent canvas and fixed limits.
  • Saving figures and ffmpeg — why saving needs fps and an encoder.
  • NumPy linspace and broadcasting — building per-frame arrays.