Tkinter中widget.destroy()内存释放异常问题咨询
问题描述
我正在学习英语,表达能力有限,抱歉。我对Tkinter的widget.destroy()有疑问:我知道这个函数应该销毁不再使用的组件以释放内存,但实际使用时发现新创建的组件编号一直在递增(比如按钮从!button到!button19),看起来组件好像没从内存里移除。我的代码每次只创建10个按钮,但多次点击后编号到了20。我想了解背后的内存使用与优化逻辑,不是要解决组件显示/隐藏的问题。
测试代码
import tkinter as tk from utils.geometrIrregular import FigurasGeometricas_irregulares minpady = 10 minpadx = 10 class Root(tk.Tk): def __init__( self): super().__init__() # Menu Buttons tk.Button( self, text = "Geometria", command = lambda: self.center_geometry(), width = 10, height = 5, name = 'geometria' ).place( relx = .01, rely = .2) tk.Button( self, text = "Calculadora", command = lambda: self.center_calculate(), width = 10, height = 5, name = 'calculadora' ).place( relx = .01, rely = .4) def center_geometry( self) -> None: self.clean_frame() tk.Label( self, text = "Inserta las coordenadas: " ).place( relx = .12, rely = .1) tk.Entry( self, ).place( relx = .23, rely = .1) """ bind options: FocusIn, FocusOut, <Button-1>, <KeyPress>... -> some options """ tk.Button( self, text = "Crear polinomio" ).place( relx = .2, rely = .15) def center_calculate( self) -> None: """ we are going to create the tk.widgets in the central part """ self.clean_frame() self.memory_lst = [] self.calculate_history = tk.StringVar() self.value = tk.StringVar() self.result = tk.StringVar() self.memory = tk.StringVar() tk.Label( self, text = 'Last operation:' ).place( relx = .2, rely = .1) tk.Entry( self, background = 'light grey', textvariable = self.calculate_history ).place( relx = .25, rely = .1) tk.Label( self, text = 'Result:' ).place( relx = .2, rely = .15) tk.Label( self, background = 'snow', textvariable = self.result ).place( relx = .25, rely = .15) tk.Label( self, text = 'Operation:' ).place( relx = .20, rely = .2) tk.Entry( self, background = 'white', textvariable = self.value ).place( relx = .25, rely = .2) # First buttons row tk.Button( self, text = " + " ).place( relx = .25, rely = .25) tk.Button( self, text = " - " ).place( relx = .27, rely = .25) tk.Button( self, text = " / " ).place( relx = .29, rely = .25) tk.Button( self, text = " * " ).place( relx = .31, rely = .25) # Second buttons row tk.Button( self, text = 'M+' ).place( relx = .25, rely = .28) tk.Button( self, text = 'M' ).place( relx = .27, rely = .28) tk.Button( self, text = 'M-' ).place( relx = .29, rely = .28) tk.Button( self, text = ' ^ ' ).place( relx = .31, rely = .28) # Thrid buttons row tk.Button( self, text = ' √ ' ).place( relx = .25, rely = .31) # Memory Lst_Box self.memory_lstbox = tk.Listbox( self, background = 'white', listvariable = self.memory, width = 50, height = 50, selectmode = tk.SINGLE ).place( relx = .6, rely = .1) # CLEAN BLOCK def clean_frame( self): for widget in self.winfo_children(): print( widget.winfo_id) if not ('geometria' in str(widget) or 'calculadora' in str(widget)): widget.destroy() if __name__ == "__main__": App = Root() App.mainloop()
运行输出
<bound method Misc.winfo_id of <tkinter.Button object .geometria>> <bound method Misc.winfo_id of <tkinter.Button object .calculadora>> <bound method Misc.winfo_id of <tkinter.Button object .geometria>> <bound method Misc.winfo_id of <tkinter.Button object .calculadora>> <bound method Misc.winfo_id of <tkinter.Label object .!label>> <bound method Misc.winfo_id of <tkinter.Entry object .!entry>> <bound method Misc.winfo_id of <tkinter.Label object .!label2>> <bound method Misc.winfo_id of <tkinter.Label object .!label3>> <bound method Misc.winfo_id of <tkinter.Label object .!label4>> <bound method Misc.winfo_id of <tkinter.Entry object .!entry2>> <bound method Misc.winfo_id of <tkinter.Button object .!button>> <bound method Misc.winfo_id of <tkinter.Button object .!button2>> <bound method Misc.winfo_id of <tkinter.Button object .!button3>> <bound method Misc.winfo_id of <tkinter.Button object .!button4>> <bound method Misc.winfo_id of <tkinter.Button object .!button5>> <bound method Misc.winfo_id of <tkinter.Button object .!button6>> <bound method Misc.winfo_id of <tkinter.Button object .!button7>> <bound method Misc.winfo_id of <tkinter.Button object .!button8>> <bound method Misc.winfo_id of <tkinter.Button object .!button9>> <bound method Misc.winfo_id of <tkinter.Listbox object .!listbox>> <bound method Misc.winfo_id of <tkinter.Button object .geometria>> <bound method Misc.winfo_id of <tkinter.Button object .calculadora>> <bound method Misc.winfo_id of <tkinter.Label object .!label5>> <bound method Misc.winfo_id of <tkinter.Entry object .!entry3>> <bound method Misc.winfo_id of <tkinter.Button object .!button10>> <bound method Misc.winfo_id of <tkinter.Button object .geometria>> <bound method Misc.winfo_id of <tkinter.Button object .calculadora>> <bound method Misc.winfo_id of <tkinter.Label object .!label6>> <bound method Misc.winfo_id of <tkinter.Entry object .!entry4>> <bound method Misc.winfo_id of <tkinter.Label object .!label7>> <bound method Misc.winfo_id of <tkinter.Label object .!label8>> <bound method Misc.winfo_id of <tkinter.Label object .!label9>> <bound method Misc.winfo_id of <tkinter.Entry object .!entry5>> <bound method Misc.winfo_id of <tkinter.Button object .!button11>> <bound method Misc.winfo_id of <tkinter.Button object .!button12>> <bound method Misc.winfo_id of <tkinter.Button object .!button13>> <bound method Misc.winfo_id of <tkinter.Button object .!button14>> <bound method Misc.winfo_id of <tkinter.Button object .!button15>> <bound method Misc.winfo_id of <tkinter.Button object .!button16>> <bound method Misc.winfo_id of <tkinter.Button object .!button17>> <bound method Misc.winfo_id of <tkinter.Button object .!button18>> <bound method Misc.winfo_id of <tkinter.Button object .!button19>> <bound method Misc.winfo_id of <tkinter.Listbox object .!listbox2>>
原理解析
1. 组件编号递增不代表内存未释放
你看到的.!button1、.!button10这类编号,是Tkinter内部维护的组件命名计数器,它的规则是单调递增——只要创建新组件,计数器就加1,不会因为旧组件被销毁而回退。这个编号只是Tk用来标识组件的唯一名称,和内存是否释放没有直接关系,完全不影响内存回收。
2. widget.destroy()的实际作用
destroy()做了两件核心事情:
- 销毁Tkinter底层(C语言实现的)组件资源,包括窗口系统的控件对象、事件绑定等,这部分资源会立即被释放;
- 断开Python层面的组件对象和Tk底层资源的关联,让Python的垃圾回收器可以处理这个对象。
但要注意:Python对象的回收需要满足没有任何引用指向它的条件。比如你代码里的self.memory_lstbox,虽然你调用了destroy(),但这个变量还持有Listbox对象的引用,Python垃圾回收器就不会回收它,直到你把self.memory_lstbox设为None或者这个Root实例被销毁。
3. 内存回收的验证方式
如果想确认内存是否真的被释放,不要看组件编号,应该用以下方法:
- 使用Python的
gc模块,统计特定类型对象的数量(比如len(gc.get_objects(types=[tk.Button]))),对比销毁前后的数量变化; - 用内存分析工具(如
memory_profiler)监控程序的内存占用情况。
4. 你的代码里的小问题
你在clean_frame里用'geometria' in str(widget)判断是否保留组件,这个方法不可靠——因为str(widget)的输出可能随Python版本变化。正确的做法是用widget.winfo_name()直接获取组件的名称:
if widget.winfo_name() not in ('geometria', 'calculadora'): widget.destroy()
内容的提问来源于stack exchange,提问作者HombreconNombre

