Tkinter销毁组件未释放内存问题求助
问题描述
我编写了一段Tkinter代码,通过按钮实现图表更新(销毁后重新绘制):点击按钮时销毁底部框架内的图表组件,再重新生成图表。代码如下:
import tkinter as tk from tkinter import ttk from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from matplotlib.figure import Figure import numpy as np class Main_Program(tk.Tk): def __init__(self, *args, **kwargs): tk.Tk.__init__(self, *args, **kwargs) self.geometry("500x500") # ---------------------------------------- TOP FRAME (Button) self.top_frame = tk.Frame(self) ttk.Button(self.top_frame,text="Update graph",command=self.update_graph).pack() self.top_frame.pack() # ---------------------------------------- BOTTOM FRAME (Graph) self.bottom_frame = tk.Frame(self) DisplayGraph(self.bottom_frame, x_limit= (-10, 10) , delta_x= 0.1 , y_input= "2*x+2" ) self.bottom_frame.pack() def update_graph(self): # ----------------------------- DESTROY THE FRAME for widget in self.bottom_frame.winfo_children(): widget.destroy() # ----------------------------- DISPLAY NEW GRAPH DisplayGraph(self.bottom_frame, x_limit= (-10, 10) , delta_x= 0.1 , y_input= "2*x+2" ) class DisplayGraph: def __init__(self,passed_frame, x_limit=(), # TUPLE (-10,10) delta_x=0.0, # FLOAT 0.1 y_input='', # STRING 2*x+2 figsize_x=6, figsize_y=5): self.x_limit = x_limit self.delta_x = delta_x self.y_input = y_input # ---------------------------------------------------------- CREATE FIGURE AND SUBPLOT self.fig = Figure(figsize=(figsize_x,figsize_y),dpi=100) self.ax = self.fig.add_subplot(111) self.ax.grid() # ---------------------------------------------------------- PLOT self.plot_input() # ---------------------------------------------------------- PACK AND DRAW self.canvas_frame = tk.Frame(passed_frame,bg="black") self.canvas_mat = FigureCanvasTkAgg(self.fig, master=self.canvas_frame) self.canvas_mat.get_tk_widget().pack(anchor="center", expand=True,padx=2,pady=2) self.canvas_frame.pack(side=tk.LEFT,anchor="center", expand=True) self.canvas_mat.draw() # ---------------------------------------------------------- def plot_input(self): x = np.arange(self.x_limit[0],self.x_limit[1],self.delta_x) y = eval(self.y_input) self.ax.plot(x,y) App = Main_Program() App.mainloop()
多次点击按钮后,通过任务管理器观察到Python脚本的内存占用持续增长。我尝试过使用gc.collect()清理未引用对象,以及pack_forget()方法,均无法解决问题。请问该如何避免这种内存增长?不确定问题是否与垃圾回收或destroy方法相关。
问题原因与解决方法
核心原因
每次点击按钮都会创建新的Figure、FigureCanvasTkAgg对象,但旧对象与Tkinter之间存在未被切断的隐式引用链,导致Python垃圾回收机制无法正确回收这些资源,最终引发内存泄漏。单纯调用widget.destroy()只能销毁Tkinter层面的组件,无法清理matplotlib内部的关联资源。
优化方案
方案1:复用已有绘图资源(推荐)
不需要每次销毁重建整个图表,而是复用已有的Figure和Axes,仅更新绘图数据。这种方式从根源避免了重复创建资源,是最高效的解决方案:
修改后的完整代码:
import tkinter as tk from tkinter import ttk from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from matplotlib.figure import Figure import numpy as np class Main_Program(tk.Tk): def __init__(self, *args, **kwargs): tk.Tk.__init__(self, *args, **kwargs) self.geometry("500x500") # 顶部按钮框架 self.top_frame = tk.Frame(self) ttk.Button(self.top_frame,text="Update graph",command=self.update_graph).pack() self.top_frame.pack() # 底部图表框架 self.bottom_frame = tk.Frame(self) # 保存DisplayGraph实例,方便后续更新 self.graph_instance = DisplayGraph(self.bottom_frame, x_limit=(-10, 10), delta_x=0.1, y_input="2*x+2") self.bottom_frame.pack() def update_graph(self): # 直接调用已有实例的更新方法,无需销毁重建 self.graph_instance.update_plot( x_limit=(-10, 10), delta_x=0.1, y_input="2*x+2" # 这里可以改成不同的表达式测试,比如"x**2" ) class DisplayGraph: def __init__(self,passed_frame, x_limit=(), delta_x=0.0, y_input='', figsize_x=6, figsize_y=5): self.x_limit = x_limit self.delta_x = delta_x self.y_input = y_input # 创建一次Figure和Axes self.fig = Figure(figsize=(figsize_x,figsize_y),dpi=100) self.ax = self.fig.add_subplot(111) self.ax.grid() # 创建画布并绑定 self.canvas_frame = tk.Frame(passed_frame,bg="black") self.canvas_mat = FigureCanvasTkAgg(self.fig, master=self.canvas_frame) self.canvas_mat.get_tk_widget().pack(anchor="center", expand=True,padx=2,pady=2) self.canvas_frame.pack(side=tk.LEFT,anchor="center", expand=True) # 初次绘图 self.plot_input() self.canvas_mat.draw() def plot_input(self): x = np.arange(self.x_limit[0],self.x_limit[1],self.delta_x) y = eval(self.y_input) self.ax.plot(x,y) def update_plot(self, x_limit, delta_x, y_input): # 清空当前Axes的内容 self.ax.clear() self.ax.grid() # 重新添加网格 # 更新参数并绘图 self.x_limit = x_limit self.delta_x = delta_x self.y_input = y_input self.plot_input() # 重新绘制画布 self.canvas_mat.draw() App = Main_Program() App.mainloop()
方案2:手动清理所有关联资源(若必须销毁重建)
如果坚持使用销毁重建的逻辑,需要手动断开matplotlib画布与Tkinter的绑定,并清理所有相关引用,确保垃圾回收能正常工作:
修改代码如下:
import tkinter as tk from tkinter import ttk from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from matplotlib.figure import Figure import numpy as np import gc class Main_Program(tk.Tk): def __init__(self, *args, **kwargs): tk.Tk.__init__(self, *args, **kwargs) self.geometry("500x500") self.top_frame = tk.Frame(self) ttk.Button(self.top_frame,text="Update graph",command=self.update_graph).pack() self.top_frame.pack() self.bottom_frame = tk.Frame(self) # 保存图表实例引用 self.graph_instance = None self.create_graph() self.bottom_frame.pack() def create_graph(self): self.graph_instance = DisplayGraph(self.bottom_frame, x_limit=(-10, 10), delta_x=0.1, y_input="2*x+2") def update_graph(self): # 先手动清理旧图表的资源 if self.graph_instance: # 断开画布与Tkinter的绑定 self.graph_instance.canvas_mat.get_tk_widget().destroy() self.graph_instance.canvas_mat.figure.clear() # 清理所有引用 del self.graph_instance.canvas_mat del self.graph_instance.fig del self.graph_instance.ax del self.graph_instance # 强制触发垃圾回收 gc.collect() # 销毁框架内剩余组件 for widget in self.bottom_frame.winfo_children(): widget.destroy() # 创建新图表 self.create_graph() class DisplayGraph: def __init__(self,passed_frame, x_limit=(), delta_x=0.0, y_input='', figsize_x=6, figsize_y=5): self.x_limit = x_limit self.delta_x = delta_x self.y_input = y_input self.fig = Figure(figsize=(figsize_x,figsize_y),dpi=100) self.ax = self.fig.add_subplot(111) self.ax.grid() self.plot_input() self.canvas_frame = tk.Frame(passed_frame,bg="black") self.canvas_mat = FigureCanvasTkAgg(self.fig, master=self.canvas_frame) self.canvas_mat.get_tk_widget().pack(anchor="center", expand=True,padx=2,pady=2) self.canvas_frame.pack(side=tk.LEFT,anchor="center", expand=True) self.canvas_mat.draw() def plot_input(self): x = np.arange(self.x_limit[0],self.x_limit[1],self.delta_x) y = eval(self.y_input) self.ax.plot(x,y) App = Main_Program() App.mainloop()
关键说明
- 方案1通过复用资源避免了重复创建对象,内存占用会保持稳定,是优先选择的方案。
- 方案2需要手动清理matplotlib的内部资源并切断引用,同时强制触发垃圾回收,才能有效释放内存。
内容的提问来源于stack exchange,提问作者cenfra
相关产品推荐
相关产品推荐

