对于Python中的gc,动态创建的类总是“无法访问”吗?

时间:2014-03-20 23:23:55

标签: python garbage-collection

我对Python中的垃圾收集有疑问。在阅读了一些有关为什么人们可能更愿意运行带有禁用垃圾收集*的Python程序的有见地的文章之后,我决定搜索并删除代码中的所有循环引用,以允许通过单独的ref-count来销毁对象。

为了找到现有的循环引用,我在我的unittest案例的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是在函数栏的范围内创建的,并且永远不会返回到外部,但它仍然存在并导致垃圾收集器找到无法访问的对象。

当将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开销包含在一些特定函数中似乎是一种合理的折衷。

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

1 个答案:

答案 0 :(得分:3)

声明一个新式的类 - 静态或通过type - 创建一个循环引用(实际上,不止一个)。这是我能提供的最明显的例子:

class Baz:
    pass

print(Baz in Baz.__mro__)
#True

Baz的{​​{1}}中还有一些其他的循环引用,但只有一个是你需要的。

我没有提供任何解决方法 - 这就是GC的用途,我很害怕。如果您想进一步潜水,我可以指出this bug report已经存在了一段时间。