Python:exec语句和意外的垃圾收集器行为

3xt*_*ter 7 python garbage-collection exec

我发现了一个问题exec(它发生在一个必须可以用用户编写的脚本扩展的系统中).我可以将问题本身减少到这段代码:

def fn():
    context = {}
    exec '''
class test:
    def __init__(self):
        self.buf = '1'*1024*1024*200
x = test()''' in context

fn()
Run Code Online (Sandbox Code Playgroud)

我希望在调用函数后,垃圾收集器应该释放内存fn.但是,Python进程仍然消耗额外的200MB内存,我完全不知道这里发生了什么以及如何手动释放分配的内存.

我怀疑在里面定义一个类exec并不是一个非常明智的想法,但首先,我想了解上面例子中出了什么问题.

看起来在另一个函数中创建包装类实例可以解决问题,但有什么区别?

def fn():
    context = {}
    exec '''
class test:
    def __init__(self):
        self.buf = '1'*1024*1024*200
def f1(): x = test()
f1()
    ''' in context
fn()
Run Code Online (Sandbox Code Playgroud)

这是我的Python解释器版本:

$ python
Python 2.7 (r27:82500, Sep 16 2010, 18:02:00) 
[GCC 4.5.1 20100907 (Red Hat 4.5.1-3)] on linux2
Run Code Online (Sandbox Code Playgroud)

Tho*_*ers 5

那你看到它占用200MB的内存比预期的更长的原因是因为你有一个参考周期:context是一个字典引用都xtest.x引用的实例test,其引用test.test有一个属性的字典test.__dict__,它包含__init__类的功能.该__init__函数依次引用它所定义的全局变量 - 这是你传递给的字典exec,context.

Python将为您打破这些参考周期(因为没有任何涉及的__del__方法),但它需要gc.collect()运行.gc.collect()将自动运行每N次分配(确定gc.set_threshold()),因此"泄漏"将在某一时刻消失,但如果您希望它立即消失,您可以gc.collect()自己运行,或者在退出函数之前自行中断参考循环.您可以通过调用轻松完成后者context.clear()- 但您应该意识到这会影响您在其中创建的类的所有实例.