Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 17fef88

Browse files
committed
Fix passing iterator as frames to FuncAnimation
1 parent f2e0479 commit 17fef88

File tree

2 files changed

+29
-0
lines changed

2 files changed

+29
-0
lines changed

lib/matplotlib/animation.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import abc
2020
import base64
2121
import contextlib
22+
import copy
2223
from io import BytesIO, TextIOWrapper
2324
import itertools
2425
import logging
@@ -1545,6 +1546,14 @@ def func(frame, *fargs) -> iterable_of_artists
15451546
- If an iterable, then simply use the values provided. If the
15461547
iterable has a length, it will override the *save_count* kwarg.
15471548
1549+
Note that when using ``repeat=True`` (the default) *frames* must
1550+
be iterable multiple times. This is true for most common iterables.
1551+
1552+
However, e.g. generator expressions and possibly some custom classes
1553+
that implement the iterator protocol cannot be used. Technically we
1554+
require either ``iter(frames) is not frames`` or *frames* must
1555+
support ``copy.copy(frames)``).
1556+
15481557
- If an integer, then equivalent to passing ``range(frames)``
15491558
15501559
- If a generator function, then must have the signature::
@@ -1623,6 +1632,19 @@ def __init__(self, fig, func, frames=None, init_func=None, fargs=None,
16231632
elif callable(frames):
16241633
self._iter_gen = frames
16251634
elif np.iterable(frames):
1635+
if iter(frames) is not frames or not kwargs.get('repeat', True):
1636+
self._iter_gen = lambda: iter(frames)
1637+
else:
1638+
# Since repeat=True we copy frames, to ensure that _iter_gen()
1639+
# returns a new iterator on each call.
1640+
try:
1641+
# Fail early if we cannot copy frames.
1642+
copy.copy(frames)
1643+
except TypeError:
1644+
raise ValueError(
1645+
'frames must be iterable multiple times if '
1646+
'repeat=True.')
1647+
self._iter_gen = lambda: iter(copy.copy(frames))
16261648
self._iter_gen = lambda: iter(frames)
16271649
if hasattr(frames, '__len__'):
16281650
self.save_count = len(frames)

lib/matplotlib/tests/test_animation.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,13 @@ def test_no_length_frames():
186186
.save('unused.null', writer=NullMovieWriter()))
187187

188188

189+
def test_generator_expression_repeat():
190+
"""A generator expression cannot be used for frames if repeat=True"""
191+
with pytest.raises(ValueError) as e:
192+
make_animation(frames=(i for i in range(5)), repeat=True)
193+
assert 'frames must be iterable multiple times' in str(e)
194+
195+
189196
def test_movie_writer_registry():
190197
ffmpeg_path = mpl.rcParams['animation.ffmpeg_path']
191198
# Not sure about the first state as there could be some writer

0 commit comments

Comments
 (0)