python - reload()时gc不可达

2 投票
2 回答
596 浏览
提问于 2025-04-18 18:33

我有一段代码,保存为 so.py:

import gc
gc.set_debug(gc.DEBUG_STATS|gc.DEBUG_LEAK)

class GUI():
    #########################################
    def set_func(self):
        self.functions = {}
        self.functions[100] = self.userInput
    #########################################
    def userInput(self):
        a = 1
g = GUI()
g.set_func()
print gc.collect()
print gc.garbage

这是运行后的输出:

在这里输入图片描述

我有两个问题:

  1. 为什么在第一次导入时,gc.collect() 不报告不可达?而是只有在 reload() 时才报告不可达

  2. 有没有什么快速的方法来修复这个函数映射的循环引用,比如 self.functions[100] = self.userInput?因为我以前的项目有很多这样的循环引用,我在寻找一种快速的方法或者一行代码来修改这些代码。目前我做的是在最后对所有这些函数使用 "del g.functions"。

2 个回答

1
  1. reload的作用是重新执行模块。新的定义会替代旧的定义,这样旧的值就无法再被访问了。相比之下,在第一次导入时,没有被替代的定义,所以自然也就没有什么会变得无法访问。

  2. 一种方法是将functions对象作为参数传递给set_func,而不是将其作为实例属性来赋值。这样可以打破循环,同时仍然可以将functions对象传递到需要的地方。

5
  1. 第一次导入这个模块时,什么都不会被清理,因为你有一个指向so模块的引用,所有其他对象都是通过它来引用的,所以它们都还在,垃圾回收器没有东西可以清理。

    当你执行reload(so)时,模块会重新运行,这样就覆盖了之前的所有引用,因此现在那些旧值就没有任何引用了。

    在这里你确实有一个引用循环:

     self.functions[100] = self.userInput
    

    因为self.userInput是一个绑定的方法,它有一个指向self的引用。所以现在self指向functions字典,而这个字典又指向userInput这个绑定的方法,这个方法又指向self,所以垃圾回收器会清理这些对象。

  2. 这要看你想做什么。从你的代码来看,不太清楚你是怎么使用self.functions这个字典的,具体情况可能有不同的解决方案。

    打破循环的最简单方法就是创建self.functions属性,而是明确地传递这个字典。

    如果self.functions只引用绑定的方法,你可以存储方法的名称而不是方法本身:

    self.functions[100] = self.userInput.__name__
    

    然后你可以通过以下方式调用这个方法:

    getattr(self, self.functions[100])()
    

    或者你可以这样做:

    from operator import methodcaller
    call_method = methodcaller(self.functions[100])
    call_method(self)   # calls self.userInput()
    

我不太明白你说的“目前我做的是在最后用del g.functions删除所有这些函数。”你指的是哪些函数呢?

另外,这真的是个问题吗?你真的遇到了内存泄漏吗?

请注意,垃圾回收器报告对象为不可达而不是不可回收。这意味着即使这些对象是引用循环的一部分,它们也会被释放。所以不应该发生内存泄漏。

实际上,添加del g.functions没用的,因为这些对象反正会被释放,所以最简单的解决办法就是直接删除所有这些del语句,因为它们根本没有任何作用。

它们被放入gc.garbage中是因为gc.DEBUG_LEAK意味着启用了GC.DEBUG_SAVEALL这个标志,这会让垃圾回收器把所有不可达的对象放入garbage中,而不仅仅是不可回收的对象。

撰写回答