Skip to content

Commit ec60128

Browse files
oscargustacaswell
authored andcommitted
Deprecate attributes and expire deprecation in animation
1 parent c1588e6 commit ec60128

File tree

6 files changed

+41
-39
lines changed

6 files changed

+41
-39
lines changed

.circleci/config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ jobs:
222222
- doc-deps-install
223223

224224
- doc-build
225+
225226
- doc-show-errors-warnings
226227
- doc-show-deprecations
227228

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Passing None as ``save_count``
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
... to `.FuncAnimation` no longer limits the number of frames to 100. Make
5+
sure that it either can be inferred from *frames* or provide an integer
6+
*save_count*.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
``Animation`` attributes
2+
~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
The attributes ``repeat`` of `.TimedAnimation` and ``save_count`` of
5+
`.FuncAnimation` are considered private and deprecated.

examples/animation/animate_decay.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,7 @@ def run(data):
5050

5151
return line,
5252

53-
ani = animation.FuncAnimation(fig, run, data_gen, interval=100, init_func=init)
53+
# Only save last 100 frames, but run forever
54+
ani = animation.FuncAnimation(fig, run, data_gen, interval=100, init_func=init,
55+
save_count=100)
5456
plt.show()

lib/matplotlib/animation.py

Lines changed: 25 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1084,7 +1084,7 @@ def _pre_composite_to_white(color):
10841084
frame_number = 0
10851085
# TODO: Currently only FuncAnimation has a save_count
10861086
# attribute. Can we generalize this to all Animations?
1087-
save_count_list = [getattr(a, 'save_count', None)
1087+
save_count_list = [getattr(a, '_save_count', None)
10881088
for a in all_anim]
10891089
if None in save_count_list:
10901090
total_frames = None
@@ -1237,7 +1237,7 @@ def to_html5_video(self, embed_limit=None):
12371237
This saves the animation as an h264 video, encoded in base64
12381238
directly into the HTML5 video tag. This respects :rc:`animation.writer`
12391239
and :rc:`animation.bitrate`. This also makes use of the
1240-
``interval`` to control the speed, and uses the ``repeat``
1240+
*interval* to control the speed, and uses the *repeat*
12411241
parameter to decide whether to loop.
12421242
12431243
Parameters
@@ -1300,7 +1300,7 @@ def to_html5_video(self, embed_limit=None):
13001300
options = ['controls', 'autoplay']
13011301

13021302
# If we're set to repeat, make it loop
1303-
if hasattr(self, 'repeat') and self.repeat:
1303+
if getattr(self, '_repeat', False):
13041304
options.append('loop')
13051305

13061306
return VIDEO_TAG.format(video=self._base64_video,
@@ -1321,17 +1321,18 @@ def to_jshtml(self, fps=None, embed_frames=True, default_mode=None):
13211321
embed_frames : bool, optional
13221322
default_mode : str, optional
13231323
What to do when the animation ends. Must be one of ``{'loop',
1324-
'once', 'reflect'}``. Defaults to ``'loop'`` if ``self.repeat``
1325-
is True, otherwise ``'once'``.
1324+
'once', 'reflect'}``. Defaults to ``'loop'`` if the *repeat*
1325+
parameter is True, otherwise ``'once'``.
13261326
"""
13271327
if fps is None and hasattr(self, '_interval'):
13281328
# Convert interval in ms to frames per second
13291329
fps = 1000 / self._interval
13301330

13311331
# If we're not given a default mode, choose one base on the value of
1332-
# the repeat attribute
1332+
# the _repeat attribute
13331333
if default_mode is None:
1334-
default_mode = 'loop' if self.repeat else 'once'
1334+
default_mode = 'loop' if getattr(self, '_repeat',
1335+
False) else 'once'
13351336

13361337
if not hasattr(self, "_html_representation"):
13371338
# Can't open a NamedTemporaryFile twice on Windows, so use a
@@ -1395,13 +1396,12 @@ class TimedAnimation(Animation):
13951396
blit : bool, default: False
13961397
Whether blitting is used to optimize drawing.
13971398
"""
1398-
13991399
def __init__(self, fig, interval=200, repeat_delay=0, repeat=True,
14001400
event_source=None, *args, **kwargs):
14011401
self._interval = interval
14021402
# Undocumented support for repeat_delay = None as backcompat.
14031403
self._repeat_delay = repeat_delay if repeat_delay is not None else 0
1404-
self.repeat = repeat
1404+
self._repeat = repeat
14051405
# If we're not given an event source, create a new timer. This permits
14061406
# sharing timers between animation objects for syncing animations.
14071407
if event_source is None:
@@ -1418,7 +1418,7 @@ def _step(self, *args):
14181418
# back.
14191419
still_going = super()._step(*args)
14201420
if not still_going:
1421-
if self.repeat:
1421+
if self._repeat:
14221422
# Restart the draw loop
14231423
self._init_draw()
14241424
self.frame_seq = self.new_frame_seq()
@@ -1438,6 +1438,8 @@ def _step(self, *args):
14381438
self.event_source.interval = self._interval
14391439
return True
14401440

1441+
repeat = _api.deprecated("3.7")(property(lambda self: self._repeat))
1442+
14411443

14421444
class ArtistAnimation(TimedAnimation):
14431445
"""
@@ -1594,7 +1596,7 @@ def init_func() -> iterable_of_artists
15941596
Additional arguments to pass to each call to *func*. Note: the use of
15951597
`functools.partial` is preferred over *fargs*. See *func* for details.
15961598
1597-
save_count : int, default: 100
1599+
save_count : int, optional
15981600
Fallback for the number of values from *frames* to cache. This is
15991601
only used if the number of frames cannot be inferred from *frames*,
16001602
i.e. when it's an iterator without length or a generator.
@@ -1619,7 +1621,6 @@ def init_func() -> iterable_of_artists
16191621
Whether frame data is cached. Disabling cache might be helpful when
16201622
frames contain large objects.
16211623
"""
1622-
16231624
def __init__(self, fig, func, frames=None, init_func=None, fargs=None,
16241625
save_count=None, *, cache_frame_data=True, **kwargs):
16251626
if fargs:
@@ -1632,7 +1633,7 @@ def __init__(self, fig, func, frames=None, init_func=None, fargs=None,
16321633
# Amount of framedata to keep around for saving movies. This is only
16331634
# used if we don't know how many frames there will be: in the case
16341635
# of no generator or in the case of a callable.
1635-
self.save_count = save_count
1636+
self._save_count = save_count
16361637
# Set up a function that creates a new iterable when needed. If nothing
16371638
# is passed in for frames, just use itertools.count, which will just
16381639
# keep counting from 0. A callable passed in for frames is assumed to
@@ -1652,19 +1653,10 @@ def iter_frames(frames=frames):
16521653
else:
16531654
self._iter_gen = lambda: iter(frames)
16541655
if hasattr(frames, '__len__'):
1655-
self.save_count = len(frames)
1656+
self._save_count = len(frames)
16561657
else:
16571658
self._iter_gen = lambda: iter(range(frames))
1658-
self.save_count = frames
1659-
1660-
if self.save_count is None:
1661-
# If we're passed in and using the default, set save_count to 100.
1662-
self.save_count = 100
1663-
else:
1664-
# itertools.islice returns an error when passed a numpy int instead
1665-
# of a native python int (https://bugs.python.org/issue30537).
1666-
# As a workaround, convert save_count to a native python int.
1667-
self.save_count = int(self.save_count)
1659+
self._save_count = frames
16681660

16691661
self._cache_frame_data = cache_frame_data
16701662

@@ -1691,26 +1683,18 @@ def new_saved_frame_seq(self):
16911683
self._old_saved_seq = list(self._save_seq)
16921684
return iter(self._old_saved_seq)
16931685
else:
1694-
if self.save_count is not None:
1695-
return itertools.islice(self.new_frame_seq(), self.save_count)
1696-
1697-
else:
1686+
if self._save_count is None:
16981687
frame_seq = self.new_frame_seq()
16991688

17001689
def gen():
17011690
try:
1702-
for _ in range(100):
1691+
while True:
17031692
yield next(frame_seq)
17041693
except StopIteration:
17051694
pass
1706-
else:
1707-
_api.warn_deprecated(
1708-
"2.2", message="FuncAnimation.save has truncated "
1709-
"your animation to 100 frames. In the future, no "
1710-
"such truncation will occur; please pass "
1711-
"'save_count' accordingly.")
1712-
17131695
return gen()
1696+
else:
1697+
return itertools.islice(self.new_frame_seq(), self._save_count)
17141698

17151699
def _init_draw(self):
17161700
super()._init_draw()
@@ -1751,7 +1735,8 @@ def _draw_frame(self, framedata):
17511735

17521736
# Make sure to respect save_count (keep only the last save_count
17531737
# around)
1754-
self._save_seq = self._save_seq[-self.save_count:]
1738+
if self._save_count is not None:
1739+
self._save_seq = self._save_seq[-self._save_count:]
17551740

17561741
# Call the func with framedata and args. If blitting is desired,
17571742
# func needs to return a sequence of any artists that were modified.
@@ -1777,3 +1762,6 @@ def _draw_frame(self, framedata):
17771762

17781763
for a in self._drawn_artists:
17791764
a.set_animated(self._blit)
1765+
1766+
save_count = _api.deprecated("3.7")(
1767+
property(lambda self: self._save_count))

lib/matplotlib/tests/test_animation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def test_null_movie_writer(anim):
8787
# output to an opaque background
8888
for k, v in savefig_kwargs.items():
8989
assert writer.savefig_kwargs[k] == v
90-
assert writer._count == anim.save_count
90+
assert writer._count == anim._save_count
9191

9292

9393
@pytest.mark.parametrize('anim', [dict(klass=dict)], indirect=['anim'])

0 commit comments

Comments
 (0)