You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)

问题根源

  1. 旧资源未清理:Jupyter内核中全局变量不会自动重置,每次重运行时,旧的FuncAnimation、fig等对象仍在后台占用资源,干扰新动画的事件循环。
  2. exit()错误使用:exit()会直接终止代码执行上下文,但无法正确清理动画的事件源,导致后续新动画无法正常初始化。
  3. 后端冲突:同时声明%matplotlib ipympl和%matplotlib widget会引发后端资源竞争,首次运行可能侥幸正常,但重运行时必然出问题。
  4. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 08:05:16