背景
最近在开发过程中,我想要动态生成图表,故接触到了animation.FuncAnimation这个函数。此函数会间隔一段时间动态更新图表。一般来说,执行更新的函数被命名为update。
奇怪的是,我每次运行这个函数,update总会被预料的多执行一次?这是为什么呢?研究之后遂有此文
结论
update会在一开始的时候被额外调用一次,以生成初始化图案
现象推理
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimationfig, ax = plt.subplots()def update(frame):# 更新画面ax.plot(x, y)print("frame:",frame)ani = FuncAnimation(fig, update, frames=range(10), interval=100)
plt.show()
此时输出为:
frame:0
frame:0
frame:1
frame:2
frame:3
frame:4
frame:5
frame:6
frame:7
frame:8
frame:9
可以看到,frame0居然被执行了两次。非常神奇和奇怪。
源码分析
def _init_draw(self):super()._init_draw()# Initialize the drawing either using the given init_func or by# calling the draw function with the first item of the frame sequence.# For blitting, the init_func should return a sequence of modified# artists.if self._init_func is None:try:frame_data = next(self.new_frame_seq())except StopIteration:# we can't start the iteration, it may have already been# exhausted by a previous save or just be 0 length.# warn and bail.warnings.warn("Can not start iterating the frames for the initial draw. ""This can be caused by passing in a 0 length sequence ""for *frames*.\n\n""If you passed *frames* as a generator ""it may be exhausted due to a previous display or save.")returnself._draw_frame(frame_data)else:self._drawn_artists = self._init_func()if self._blit:if self._drawn_artists is None:raise RuntimeError('The init_func must return a ''sequence of Artist objects.')for a in self._drawn_artists:a.set_animated(self._blit)self._save_seq = []def _draw_frame(self, framedata):if self._cache_frame_data:# Save the data for potential saving of movies.self._save_seq.append(framedata)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.self._drawn_artists = self._func(framedata, *self._args)if self._blit:err = RuntimeError('The animation function must return a sequence ''of Artist objects.')try:# check if a sequenceiter(self._drawn_artists)except TypeError:raise err from None# check each item if it's artistfor i in self._drawn_artists:if not isinstance(i, mpl.artist.Artist):raise errself._drawn_artists = sorted(self._drawn_artists,key=lambda x: x.get_zorder())for a in self._drawn_artists:a.set_animated(self._blit)save_count = _api.deprecate_privatize_attribute("3.7")
其中 self._func() 是我们的update函数,在这里的 _init_draw 函数中,若没有初始化则,执行一次_draw_frame 操作。再此操作中会声明如下:
self._drawn_artists = self._func(framedata, *self._args)
显然在此初始化过程中,我们执行了一次update。
结论
update会在一开始的时候被额外调用一次,以生成初始化图案
我们可以使用如下代码来规避这个过程:
flag = False
# 定义一个更新函数用于动画
def update(i):global flagax.clear() ax.set_xlabel('Recall')ax.set_ylabel('Precision')title = f'Precision-Recall Curve for Class {i} (area = {average_precision[i]:0.2f})'if flag == False:line, = ax.plot(recall[i], precision[i], color=plt.cm.viridis(i / len(np.unique(y_test))))flag = Trueax.set_title(title)return line,else:line, = ax.plot(recall[i], precision[i], label=f'Class {i}', color=plt.cm.viridis(i / len(np.unique(y_test))))ax.legend()print("i = ", i)ax.set_title(title)return line,