-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Deprecate attributes and expire deprecation in animation #24131
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
ec60128
Deprecate attributes and expire deprecation in animation
oscargus 96fcb93
MNT: add warnings if we ignore user passed `save_count`
tacaswell dabd667
MNT: add warning if we disable cache_frame_data
tacaswell 384b1eb
MNT: if we are caching frames then we have a save_count
tacaswell bbeb86f
TST: add tests for warnings and avoid warnings in tests
tacaswell 790c743
DOC: set save count in 3 animation examples
tacaswell 13f398e
Fixup
oscargus d483a96
Update doc/api/next_api_changes/behavior/24131-OG.rst
oscargus 046d0c2
DOC: fix markup
tacaswell a253f22
Fix doc-build
oscargus File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
``FuncAnimation(save_count=None)`` | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
Passing ``save_count=None`` to `.FuncAnimation` no longer limits the number | ||
of frames to 100. Make sure that it either can be inferred from *frames* | ||
or provide an integer *save_count*. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
``Animation`` attributes | ||
~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
The attributes ``repeat`` of `.TimedAnimation` and subclasses and | ||
``save_count`` of `.FuncAnimation` are considered private and deprecated. |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -247,7 +247,7 @@ class MovieWriter(AbstractMovieWriter): | |
The format used in writing frame data, defaults to 'rgba'. | ||
fig : `~matplotlib.figure.Figure` | ||
The figure to capture data from. | ||
This must be provided by the sub-classes. | ||
This must be provided by the subclasses. | ||
""" | ||
|
||
# Builtin writer subclasses additionally define the _exec_key and _args_key | ||
|
@@ -1084,7 +1084,7 @@ def _pre_composite_to_white(color): | |
frame_number = 0 | ||
# TODO: Currently only FuncAnimation has a save_count | ||
# attribute. Can we generalize this to all Animations? | ||
save_count_list = [getattr(a, 'save_count', None) | ||
save_count_list = [getattr(a, '_save_count', None) | ||
for a in all_anim] | ||
if None in save_count_list: | ||
total_frames = None | ||
|
@@ -1237,7 +1237,7 @@ def to_html5_video(self, embed_limit=None): | |
This saves the animation as an h264 video, encoded in base64 | ||
directly into the HTML5 video tag. This respects :rc:`animation.writer` | ||
and :rc:`animation.bitrate`. This also makes use of the | ||
``interval`` to control the speed, and uses the ``repeat`` | ||
*interval* to control the speed, and uses the *repeat* | ||
parameter to decide whether to loop. | ||
|
||
Parameters | ||
|
@@ -1300,7 +1300,7 @@ def to_html5_video(self, embed_limit=None): | |
options = ['controls', 'autoplay'] | ||
|
||
# If we're set to repeat, make it loop | ||
if hasattr(self, 'repeat') and self.repeat: | ||
if getattr(self, '_repeat', False): | ||
options.append('loop') | ||
|
||
return VIDEO_TAG.format(video=self._base64_video, | ||
|
@@ -1321,17 +1321,18 @@ def to_jshtml(self, fps=None, embed_frames=True, default_mode=None): | |
embed_frames : bool, optional | ||
default_mode : str, optional | ||
What to do when the animation ends. Must be one of ``{'loop', | ||
'once', 'reflect'}``. Defaults to ``'loop'`` if ``self.repeat`` | ||
is True, otherwise ``'once'``. | ||
'once', 'reflect'}``. Defaults to ``'loop'`` if the *repeat* | ||
parameter is True, otherwise ``'once'``. | ||
""" | ||
if fps is None and hasattr(self, '_interval'): | ||
# Convert interval in ms to frames per second | ||
fps = 1000 / self._interval | ||
|
||
# If we're not given a default mode, choose one base on the value of | ||
# the repeat attribute | ||
# the _repeat attribute | ||
if default_mode is None: | ||
default_mode = 'loop' if self.repeat else 'once' | ||
default_mode = 'loop' if getattr(self, '_repeat', | ||
False) else 'once' | ||
|
||
if not hasattr(self, "_html_representation"): | ||
# Can't open a NamedTemporaryFile twice on Windows, so use a | ||
|
@@ -1395,13 +1396,12 @@ class TimedAnimation(Animation): | |
blit : bool, default: False | ||
Whether blitting is used to optimize drawing. | ||
""" | ||
|
||
def __init__(self, fig, interval=200, repeat_delay=0, repeat=True, | ||
event_source=None, *args, **kwargs): | ||
self._interval = interval | ||
# Undocumented support for repeat_delay = None as backcompat. | ||
self._repeat_delay = repeat_delay if repeat_delay is not None else 0 | ||
self.repeat = repeat | ||
self._repeat = repeat | ||
# If we're not given an event source, create a new timer. This permits | ||
# sharing timers between animation objects for syncing animations. | ||
if event_source is None: | ||
|
@@ -1418,7 +1418,7 @@ def _step(self, *args): | |
# back. | ||
still_going = super()._step(*args) | ||
if not still_going: | ||
if self.repeat: | ||
if self._repeat: | ||
# Restart the draw loop | ||
self._init_draw() | ||
self.frame_seq = self.new_frame_seq() | ||
|
@@ -1438,6 +1438,8 @@ def _step(self, *args): | |
self.event_source.interval = self._interval | ||
return True | ||
|
||
repeat = _api.deprecate_privatize_attribute("3.7") | ||
|
||
|
||
class ArtistAnimation(TimedAnimation): | ||
""" | ||
|
@@ -1594,7 +1596,7 @@ def init_func() -> iterable_of_artists | |
Additional arguments to pass to each call to *func*. Note: the use of | ||
`functools.partial` is preferred over *fargs*. See *func* for details. | ||
|
||
save_count : int, default: 100 | ||
save_count : int, optional | ||
Fallback for the number of values from *frames* to cache. This is | ||
only used if the number of frames cannot be inferred from *frames*, | ||
i.e. when it's an iterator without length or a generator. | ||
|
@@ -1619,7 +1621,6 @@ def init_func() -> iterable_of_artists | |
Whether frame data is cached. 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, *, cache_frame_data=True, **kwargs): | ||
if fargs: | ||
|
@@ -1632,7 +1633,7 @@ def __init__(self, fig, func, frames=None, init_func=None, fargs=None, | |
# Amount of framedata to keep around for saving movies. This is only | ||
# used if we don't know how many frames there will be: in the case | ||
# of no generator or in the case of a callable. | ||
self.save_count = save_count | ||
self._save_count = save_count | ||
# Set up a function that creates a new iterable when needed. If nothing | ||
# is passed in for frames, just use itertools.count, which will just | ||
# keep counting from 0. A callable passed in for frames is assumed to | ||
|
@@ -1652,19 +1653,31 @@ def iter_frames(frames=frames): | |
else: | ||
self._iter_gen = lambda: iter(frames) | ||
if hasattr(frames, '__len__'): | ||
self.save_count = len(frames) | ||
self._save_count = len(frames) | ||
if save_count is not None: | ||
_api.warn_external( | ||
f"You passed in an explicit {save_count=} " | ||
"which is being ignored in favor of " | ||
f"{len(frames)=}." | ||
) | ||
else: | ||
self._iter_gen = lambda: iter(range(frames)) | ||
self.save_count = frames | ||
|
||
if self.save_count is None: | ||
# If we're passed in and using the default, set save_count to 100. | ||
self.save_count = 100 | ||
else: | ||
# itertools.islice returns an error when passed a numpy int instead | ||
# of a native python int (https://bugs.python.org/issue30537). | ||
# As a workaround, convert save_count to a native python int. | ||
self.save_count = int(self.save_count) | ||
self._save_count = frames | ||
if save_count is not None: | ||
_api.warn_external( | ||
f"You passed in an explicit {save_count=} which is being " | ||
f"ignored in favor of {frames=}." | ||
) | ||
if self._save_count is None and cache_frame_data: | ||
_api.warn_external( | ||
f"{frames=!r} which we can infer the length of, " | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this supposed to say "which we cannot infer the length of"? |
||
"did not pass an explicit *save_count* " | ||
f"and passed {cache_frame_data=}. To avoid a possibly " | ||
"unbounded cache, frame data caching has been disabled. " | ||
"To suppress this warning either pass " | ||
"`cache_frame_data=False` or `save_count=MAX_FRAMES`." | ||
) | ||
cache_frame_data = False | ||
|
||
self._cache_frame_data = cache_frame_data | ||
|
||
|
@@ -1691,26 +1704,18 @@ def new_saved_frame_seq(self): | |
self._old_saved_seq = list(self._save_seq) | ||
return iter(self._old_saved_seq) | ||
else: | ||
if self.save_count is not None: | ||
return itertools.islice(self.new_frame_seq(), self.save_count) | ||
|
||
else: | ||
if self._save_count is None: | ||
frame_seq = self.new_frame_seq() | ||
|
||
def gen(): | ||
try: | ||
for _ in range(100): | ||
while True: | ||
yield next(frame_seq) | ||
except StopIteration: | ||
pass | ||
else: | ||
_api.warn_deprecated( | ||
"2.2", message="FuncAnimation.save has truncated " | ||
"your animation to 100 frames. In the future, no " | ||
"such truncation will occur; please pass " | ||
"'save_count' accordingly.") | ||
|
||
return gen() | ||
else: | ||
return itertools.islice(self.new_frame_seq(), self._save_count) | ||
|
||
def _init_draw(self): | ||
super()._init_draw() | ||
|
@@ -1748,10 +1753,7 @@ def _draw_frame(self, 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) | ||
self._save_seq = self._save_seq[-self.save_count:] | ||
self._save_seq = self._save_seq[-self._save_count:] | ||
|
||
# Call the func with framedata and args. If blitting is desired, | ||
# func needs to return a sequence of any artists that were modified. | ||
|
@@ -1777,3 +1779,5 @@ def _draw_frame(self, framedata): | |
|
||
for a in self._drawn_artists: | ||
a.set_animated(self._blit) | ||
|
||
save_count = _api.deprecate_privatize_attribute("3.7") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this needed for sphinx-gallery? There isn't otherwise any saving going on in any of these examples.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, SG saves both a gif thumbnail and a js_html output to embed in the docs.