【问题标题】:How to clean up tk.createcommand?如何清理 tk.createcommand?
【发布时间】:2019-04-25 14:28:59
【问题描述】:

下面的例子会因为tk.createcommand而造成内存泄漏:

import tkinter
import gc

class Foo:

    def __init__(self):
        self.tk = tkinter.Tcl()
        self.tk.createcommand("someCommand", self.someCommand)

    def someCommand(self):
        pass

if __name__ == "__main__":

    while 1:
        f = Foo()
        # f.tk.deletecommand("someCommand")
        # del f
        # gc.collect()

如果您删除 tk.createcommand 一切正常,del fgc.collect() 将不起作用,tk.deletecommand 将抛出 _tkinter.TclError: can't delete Tcl command

有什么方法可以删除创建的命令或者清理内存?我使用 tkinter GUI,我需要它来调用一些遗留的 tcl 代码。

python                                                                                                                         
Python 3.7.3 (default, Mar 26 2019, 21:43:19) 
[GCC 8.2.1 20181127] on linux

【问题讨论】:

  • 泄漏内存的不是命令,而是您创建的整个 Tcl 环境以及 Foo 的每个实例。我不知道有什么方法可以处理它。
  • 但是如果我删除了createcommand 没有内存泄漏(或者至少泄漏非常缓慢)。那么这是一个错误吗?
  • 绝对应该可以配置 Tcl 环境,或者至少配置 Tcl 解释器。这设计是一个一次性的上下文(尽管 Tk 有点麻烦)。不过似乎没有暴露。
  • @OcasoProtal:您是否需要为每个新的Foo() 提供一个新的tkinter.Tcl() 实例?
  • @stovfl 很遗憾,是的,因为遗留代码中的副作用

标签: python tkinter tcl


【解决方案1】:

虽然在完整的 Tcl 环境中解释器是完全可销毁的(如果不便宜的话)对象,但 Tkinter 自己的参考 Tcl 解释器(一个相当重量级的实体)似乎并不容易删除;它与底层 C _tkinter 对象的生命周期相关,这有点复杂……以至于我很难判断删除它并进行垃圾收集是否会导致事情变得可重新加载,或者你是否只是为未来的怪异做准备。它可能可以工作,前提是您也没有加载 Tk(因为 Tk 有点麻烦)。最终。

因此,真的最好在不需要时避免重新加载它,并且最好不要创建 Tcl() 的新实例(如果不需要)实际上并不需要它们。毕竟这有点像说做一个新的 Python 解释器……所以它可能不应该在普通代码的紧密循环中完成。

你已经找到deletecommand;命令被设计为创建和销毁都很便宜。


是否可以改为在子进程中运行代码?如果没有 Tk,那应该相当简单,并且操作系统可以很好地清理进程。有很多方法可以做到这一点;哪一个最好取决于您正在做的事情的细节(尤其是有多少交互性以及您要移动多少数据)。

【讨论】:

    【解决方案2】:

    我找到了一个解决方案,尽管这个解决方案不适用于我的情况。不知道为什么,但是我们做的比这个小演示代码多一点,所以我切换到multiprocessing

    你必须显式调用deletecommand

    import tkinter
    import gc
    
    class Foo:
    
        def __init__(self):
            self.tk = tkinter.Tcl()
            self.tk.createcommand("someCommand", self.someCommand)
            print("Foo intialized")
            # self.tk.eval('rename someCommand ""')
    
        def someCommand(self):
            pass
    
        def __del__(self):
            print("DESTRUCTOR called!")
    
    if __name__ == "__main__":
    
        while 1:
            f = Foo()
            f.tk.tk.deletecommand("someCommand")
            del f
            gc.collect()
    

    小心!将调用放入__del__ 将不起作用,因为由于内存泄漏,析构函数永远不会被调用:

    import tkinter
    import gc
    
    class Foo:
    
        def __init__(self):
            self.tk = tkinter.Tcl()
            self.tk.createcommand("someCommand", self.someCommand)
            print("Foo intialized")
            # self.tk.eval('rename someCommand ""')
    
        def someCommand(self):
            pass
    
        def __del__(self):
            f.tk.tk.deletecommand("someCommand")
            print("DESTRUCTOR called!")
    
    if __name__ == "__main__":
    
        while 1:
            f = Foo()
            # f.tk.tk.deletecommand("someCommand")
            del f
            gc.collect()
    

    【讨论】:

      猜你喜欢
      • 2020-04-16
      • 2011-04-21
      • 2017-01-02
      • 2015-12-31
      • 2015-01-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多