Matplotlib动画首次运行正常,重运行卡顿需重启内核求助
问题:VSCode Jupyter Notebook中Matplotlib动画重运行失效,仅重启内核可解决
问题描述
在VSCode的Jupyter Notebook中运行散点图动画,首次运行可正常按时间步更新物体位置,但后续重运行时,要么在中途卡住并提前打印closing!,要么无输出无报错直到内核超时。频繁重启内核严重影响工作效率。
用户提供的可复现代码:
import numpy as np import matplotlib %matplotlib ipympl %matplotlib widget import matplotlib.pyplot as plt from matplotlib import animation # Toy example timepoints = np.arange(0,1,0.02) # 100 frames xs = np.arange(1,11) ys = np.arange(1,11) block_centroids = np.dstack(np.meshgrid(xs, ys)).reshape(-1, 2) # (N, 2) initial location array for N = 100 objects rand_amp = np.random.rand(100, 2)/10 fields = [rand_amp * np.sin(i/(20*np.pi)) for i in range(len(timepoints))] # sinusoidal displacements for each object with random amplitudes fig, axes = plt.subplots(figsize=(9,5), tight_layout = True) scat = axes.scatter(xs, ys) axes.set_aspect('equal', adjustable='box') def update(val): if val == len(timepoints) - 1: print('closing!') exit() scat.set_offsets(block_centroids + fields[val]) fig.suptitle(fr'time $t = {timepoints[val]:.2f}$s') fig.canvas.draw_idle() return scat def init(): pass ani = animation.FuncAnimation(fig=fig, func=update, init_func=init, frames=len(timepoints), blit = True, interval=100)
问题根源
- 旧资源未清理:Jupyter内核中全局变量不会自动重置,每次重运行时,旧的
FuncAnimation、fig等对象仍在后台占用资源,干扰新动画的事件循环。 exit()错误使用:exit()会直接终止代码执行上下文,但无法正确清理动画的事件源,导致后续新动画无法正常初始化。- 后端冲突:同时声明
%matplotlib ipympl和%matplotlib widget会引发后端资源竞争,首次运行可能侥幸正常,但重运行时必然出问题。 blit=True下的冗余操作:手动调用fig.canvas.draw_idle()会和FuncAnimation的自动重绘逻辑冲突,导致帧更新异常。
解决方案
1. 统一Matplotlib后端
仅保留%matplotlib widget(VSCode中适配交互式动画的最优后端),删除%matplotlib ipympl。
2. 代码开头强制清理旧资源
在代码最顶部添加全局清理逻辑,确保每次重运行时重置环境:
# 强制清理所有旧图形和残留动画对象 import matplotlib.pyplot as plt plt.close('all') # 移除全局变量中的旧实例 for key in list(globals().keys()): if key in ['ani', 'fig', 'scat', 'axes']: del globals()[key]
3. 正确终止动画,替换exit()
使用动画自身的stop()方法终止,避免破坏内核执行上下文:
def update(val): if val == len(timepoints) - 1: print('closing!') ani.event_source.stop() # 安全停止动画事件源 return scat scat.set_offsets(block_centroids + fields[val]) fig.suptitle(fr'time $t = {timepoints[val]:.2f}$s') return scat
4. 显式保留动画引用
在代码末尾添加display(fig),确保动画对象不会被Jupyter的垃圾回收机制意外清理:
display(fig)
完整修正代码
# 强制清理所有旧图形和残留动画对象 import matplotlib.pyplot as plt plt.close('all') # 移除全局变量中的旧实例 for key in list(globals().keys()): if key in ['ani', 'fig', 'scat', 'axes']: del globals()[key] import numpy as np import matplotlib %matplotlib widget import matplotlib.pyplot as plt from matplotlib import animation # Toy example timepoints = np.arange(0,1,0.02) # 100 frames xs = np.arange(1,11) ys = np.arange(1,11) block_centroids = np.dstack(np.meshgrid(xs, ys)).reshape(-1, 2) # (N, 2) initial location array for N = 100 objects rand_amp = np.random.rand(100, 2)/10 fields = [rand_amp * np.sin(i/(20*np.pi)) for i in range(len(timepoints))] # sinusoidal displacements for each object with random amplitudes fig, axes = plt.subplots(figsize=(9,5), tight_layout = True) scat = axes.scatter(xs, ys) axes.set_aspect('equal', adjustable='box') def update(val): if val == len(timepoints) - 1: print('closing!') ani.event_source.stop() return scat scat.set_offsets(block_centroids + fields[val]) fig.suptitle(fr'time $t = {timepoints[val]:.2f}$s') return scat def init(): pass ani = animation.FuncAnimation(fig=fig, func=update, init_func=init, frames=len(timepoints), blit = True, interval=100) display(fig)
调试方法
- 在
update函数中添加打印语句,如print(f"当前帧:{val},时间:{timepoints[val]:.2f}"),观察帧执行顺序是否正常。 - 运行
print(plt.get_fignums()),查看当前存在的图形数量,确认旧图形是否被清理。 - 通过VSCode的Jupyter变量面板,检查是否有旧的
ani、fig对象残留。
内容的提问来源于stack exchange,提问作者gargantuar
相关产品推荐
相关产品推荐

