-
-
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
Changes from 6 commits
ec60128
96fcb93
dabd667
384b1eb
bbeb86f
790c743
13f398e
d483a96
046d0c2
a253f22
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -222,6 +222,7 @@ jobs: | |
- doc-deps-install | ||
|
||
- doc-build | ||
|
||
- doc-show-errors-warnings | ||
- doc-show-deprecations | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
Passing None as ``save_count`` | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
... 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*. | ||
oscargus marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
``Animation`` attributes | ||
~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
The attributes ``repeat`` of `.TimedAnimation` and ``save_count`` of | ||
`.FuncAnimation` are considered private and deprecated. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -50,5 +50,7 @@ def run(data): | |
|
||
return line, | ||
|
||
ani = animation.FuncAnimation(fig, run, data_gen, interval=100, init_func=init) | ||
# Only save last 100 frames, but run forever | ||
ani = animation.FuncAnimation(fig, run, data_gen, interval=100, init_func=init, | ||
save_count=100) | ||
Comment on lines
+54
to
+55
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 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 commentThe 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. |
||
plt.show() |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
<
A3E2
tool-tip id="tooltip-de34e395-f1f1-44c8-be69-6306ab219664" for="expand-down-link-9-diff-8cc60f6e5a45e5dfd502623cdb3c033d7b2bbb63a0f55d0a49485f6390bcba26" popover="manual" data-direction="ne" data-type="label" data-view-component="true" class="sr-only position-absolute">Expand Down
|
@@ -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.deprecated("3.7")(property(lambda self: self._repeat)) | ||
|
||
|
||
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,6 @@ def _draw_frame(self, framedata): | |
|
||
for a in self._drawn_artists: | ||
a.set_animated(self._blit) | ||
|
||
save_count = _api.deprecated("3.7")( | ||
property(lambda self: self._save_count)) |
Uh oh!
There was an error while loading. Please reload this page.