【问题标题】:Are dynamically created classes always "unreachable" for gc in Python?动态创建的类对于 Python 中的 gc 是否总是“无法访问”?
【发布时间】:2023-04-01 03:20:01
【问题描述】:

我有一个关于 Python 垃圾收集的问题。在阅读了一些关于为什么人们可能更喜欢在禁用垃圾收集的情况下运行 Python 程序*的有见地的文章之后,我决定搜索并删除我的代码中的所有循环引用,以便仅通过引用计数来销毁对象。

为了查找现有的循环引用,我在单元测试用例的 tearDown 方法中调用了 gc.collect() ,并在返回值 >0 时打印出警告。发现的大多数问题都可以通过重构或使用弱引用轻松解决。

不过,过了一会儿,我遇到了一个相当奇怪的问题,最好用代码来表达:

import gc
gc.disable()

def bar():
    class Foo( object ):
        pass

bar()
print( gc.collect() ) # prints 6

当移除对 bar() 的调用时,gc.collect() 返回 0,正如预期的那样。

看起来即使 Foo 是在函数 bar 的范围内创建并且从未返回到外部,它仍然存在并导致垃圾收集器找到无法访问的对象。

将 Foo 移到 bar 范围之外时,一切正常。然而,该解决方案不适用于我试图在受影响的代码中解决的问题(动态创建 ctypes.Structures 以进行序列化)。

以下两种方法也不起作用:

import gc
gc.disable()

def bar():
    type( "Foo", ( object, ), {} )

bar()
print( gc.collect() ) # prints 6 again

甚至是非常“聪明”的人:

import gc
gc.disable()

import weakref

def bar():
    weakref.ref( type( "Foo", ( object, ), {} ) )

bar()
print( gc.collect() ) # still prints 6

最重要的是,这是一个实际有效的示例......但仅在 Python2 中:

import gc
gc.disable()

def bar():
    class Foo(): # not subclassing object
        pass

bar()
print( gc.collect() ) # prints 0 - finally?

然而,上面的代码确实在 Python3 中再次打印出“6”——我怀疑,因为所有用户定义的类都是 Python3 中的新型类。

那么,我是否坚持使用 Python2,Python3 中奇怪的“无法访问的对象”,还是我必须通过手动垃圾收集来跟踪每次对 bar 的调用?

*(关于使用 gc.disable() 运行 Python 的文章)

http://pydev.blogspot.de/2014/03/should-python-garbage-collector-be.html
http://dsvensson.wordpress.com/2010/07/23/the-garbage-garbage-collector-of-python/


请参阅 roippi 的回答,了解上述行为为何符合预期。

不过,为了将来参考,这里有一个小解决方法可以解决这个特定问题。并不是说禁用 gc 对任何人来说都是正确的,但如果你觉得这对你来说是正确的,我就是这样做的:

import gc
gc.disable()

def requiresGC( func ):
    def func_wrapper( *args, **kwargs ):
        result = func( *args, **kwargs )
        gc.collect()
        return result
    return func_wrapper

@requiresGC
def bar():
    class Foo( object ):
        pass

bar()
print( gc.collect() ) # prints 0

但是请注意,如果 bar() 是定期调用的函数,则此装饰器将导致显着减速。然而,在我的情况下(序列化),情况并非如此,将 gc-overhead 包含在一些特定函数中似乎是一个合理的折衷方案。

感谢所有花时间这么快回答的人! :-)

【问题讨论】:

  • 您也可以使用weakref 模块作为不会妨碍垃圾回收的引用。
  • 我建议不要关闭 GC。它是允许代码依赖的 Python 的一部分。解释器核心创建引用循环,标准库创建引用循环,外部库创建引用循环,你可能在没有意识到的情况下创建引用循环,每个人都创建引用循环。

标签:
python
garbage-collection