动态创建的类在Python中总是“不可达”吗?
我有个关于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 个回答
声明一个新式类,无论是静态的还是通过 type
创建,都会产生循环引用(实际上,可能会有不止一个)。下面是我能提供的最清晰的例子:
class Baz:
pass
print(Baz in Baz.__mro__)
#True
在 Baz
的 __dict__
中还有其他一些循环引用,但一个就足够了。
其实我没有什么好的解决办法可以提供给你——这就是垃圾回收(GC)存在的原因,恐怕只能依靠它了。如果你想更深入了解,可以看看这个错误报告,它已经存在一段时间了。