动态创建的类在Python中总是“不可达”吗?

7 投票
1 回答
1043 浏览
提问于 2025-04-17 23:13

我有个关于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后手动进行垃圾回收呢?

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

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 个回答

4

声明一个新式类,无论是静态的还是通过 type 创建,都会产生循环引用(实际上,可能会有不止一个)。下面是我能提供的最清晰的例子:

class Baz:
    pass

print(Baz in Baz.__mro__)
#True

Baz__dict__ 中还有其他一些循环引用,但一个就足够了。

其实我没有什么好的解决办法可以提供给你——这就是垃圾回收(GC)存在的原因,恐怕只能依靠它了。如果你想更深入了解,可以看看这个错误报告,它已经存在一段时间了。

撰写回答