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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 20:05:47