使用for循环后Python Matplotlib动画程序每隔1秒卡顿
解决Matplotlib动画for循环导致的卡顿问题
问题描述
原本运行正常的Python Matplotlib柱状图动画代码,使用for循环精简重复逻辑后出现以下问题:
- 柱状图窗口每隔1秒卡顿1秒
- 鼠标悬停图表、拖动窗口时,也会出现每隔1秒的周期性卡顿
- 移除for循环后卡顿消失,但希望保留for循环的代码优化方式
硬件配置:Intel Core i5 14th Gen,32GB内存
原因分析
- 重复轴清除与重绘:每帧动画都执行
ax.clear(),完全清除轴的所有元素后重新创建柱状图,极大增加计算开销 - 冗余初始化操作:轴背景设置、关闭坐标轴等静态配置被放入动画函数内重复执行
- 高频IO与数据处理:每1秒重复读取3个CSV文件、执行两次数据合并、保存图片,IO和计算操作阻塞动画线程
- 重复布局计算:
plt.tight_layout()在每帧都调用,额外增加布局计算开销
解决方案
- 复用柱状图对象:初始化时创建Bar对象,后续仅更新高度数据,避免重复创建图形
- 静态配置前置:将轴背景、关闭坐标轴等静态操作移到动画函数外,仅执行一次
- 优化数据读取:通过文件修改时间检查,仅在文件更新时重新读取,减少IO操作
- 降低非必要操作频率:若实时保存图片非必须,可降低保存频率或放到后台线程执行
优化后的代码
import os from importlib import reload import matplotlib.pyplot as plt import pandas as pd from matplotlib.animation import FuncAnimation import cred def run_script(): # 全局配置初始化 plt.rcParams["figure.facecolor"] = "#131722" plt.style.use("fivethirtyeight") fig, axs = plt.subplots(3, 1, figsize=(10, 8), sharex=True) # 静态背景配置,仅执行一次 fig.patch.set_facecolor("#131722") for ax in axs: ax.set_facecolor("#131722") ax.axis("off") # 静态配置无需每帧重复设置 # 初始化文件路径与缓存变量 fold = os.path.join(os.getcwd(), "data") symbols = [cred.symbol0, cred.symbol1, cred.symbol2] csv_paths = [os.path.join(fold, f"{s.split(':')[1]}.csv") for s in symbols] chart_path = os.path.join(os.getcwd(), "images", "chart2.png") # 缓存文件修改时间与数据 last_modified = [0]*3 cached_data = None bars = [] def init_chart(): """初始化图表元素,仅执行一次""" nonlocal cached_data, bars # 首次读取数据 try: bankniftydata = pd.read_csv(csv_paths[0], skip_blank_lines=True) pedata = pd.read_csv(csv_paths[1], skip_blank_lines=True) cedata = pd.read_csv(csv_paths[2], skip_blank_lines=True) except Exception as e: print(f"初始化读取失败: {e}") return option_data = pd.merge(pedata, cedata, on="Time", how="inner") cached_data = pd.merge(bankniftydata, option_data, on="Time", how="inner") # 创建初始柱状图,保存Bar对象用于后续更新 for idx, (symbol, ax) in enumerate(zip(symbols, axs)): vol_col = f"{symbol.split(':')[1]}Volume" bar = ax.bar(cached_data["Time"], cached_data[vol_col], color="#9598a1") bars.append(bar) # 设置静态标题框架 ax.set_title( f"{symbol.split(':')[1]} {cred.resolution}", loc="left", color="#9598a1", fontsize=12, ) ax.autoscale(tight=True) plt.tight_layout() return bars def animatechart(i): nonlocal cached_data, last_modified reload(cred) # 检查文件是否更新,仅读取变化的文件 need_update = False for idx, path in enumerate(csv_paths): current_mtime = os.path.getmtime(path) if current_mtime > last_modified[idx]: last_modified[idx] = current_mtime need_update = True if need_update: # 重新读取数据 try: bankniftydata = pd.read_csv(csv_paths[0], skip_blank_lines=True) pedata = pd.read_csv(csv_paths[1], skip_blank_lines=True) cedata = pd.read_csv(csv_paths[2], skip_blank_lines=True) except Exception as e: print(f"读取数据失败: {e}") return bars option_data = pd.merge(pedata, cedata, on="Time", how="inner") cached_data = pd.merge(bankniftydata, option_data, on="Time", how="inner") # 更新柱状图数据,复用已有的Bar对象 if cached_data is not None: for idx, (symbol, bar_container) in enumerate(zip(symbols, bars)): vol_col = f"{symbol.split(':')[1]}Volume" # 更新每个柱子的高度 for bar, height in zip(bar_container, cached_data[vol_col]): bar.set_height(height) # 更新标题的动态日期部分 ax = axs[idx] base_title = ax.get_texts()[0].get_text().split(" ")[0] ax.set_title( f"{base_title} {cred.resolution} {cred.from_date()} {cred.to_date()}", loc="left", color="#9598a1", fontsize=12, ) axs[-1].autoscale(tight=True) plt.tight_layout() # 降低保存频率,每5帧保存一次 if i % 5 == 0: plt.savefig(chart_path, facecolor="#131722", bbox_inches="tight") return bars ani = FuncAnimation( fig, animatechart, init_func=init_chart, interval=1000, cache_frame_data=False, blit=True # 开启blit优化,只更新变化元素 ) plt.show() run_script()
关键优化点说明
init_func初始化:图表静态创建、首次数据读取仅执行一次,避免重复操作- 复用Bar对象:通过
bar.set_height()更新数据,大幅减少图形重建开销 - 文件修改时间检查:仅在文件更新时读取数据,降低IO操作频率
- 开启
blit=True:Matplotlib仅重绘变化元素,提升渲染效率 - 静态配置前置:轴背景、关闭坐标轴等操作仅执行一次,减少重复计算
内容的提问来源于stack exchange,提问作者amithapase
相关产品推荐
相关产品推荐

