超出范围时,Python不会删除变量

时间:2019-02-16 18:36:27

标签: python

考虑以下代码:

import random                                                                   

class Trie:                                                                     
    def __init__(self, children, end):                                          
        self.children = children                                                
        self.end = end                                                          

def trie_empty():                                                               
    return Trie(dict(), False)                                                  

def trie_insert(x, t):                                                          
    if not x:                                                                   
        t.end = True                                                            
        return                                                                  
    try:                                                                        
        t2 = t.children[x[0]]                                                   
    except KeyError:                                                            
        t2 = trie_empty()                                                       
        t.children[x[0]] = t2                                                     
    trie_insert(x[1:], t2)                                                      

def fill_dict(root):                                                            
    memo = dict()                                                               
    def fill(pfx='', depth=0):                                                  
        try:                                                                    
            memo[pfx]                                                           
        except KeyError:                                                        
            pass                                                                
        else:                                                                   
            return                                                              
        if depth > 6:                                                           
            return                                                              
        for ci in range(ord('a'), ord('d') + 1):                                
            fill(pfx + chr(ci), depth + 1)                                      
        bw = None                                                               
        memo[pfx] = None, bw                                                    
    fill()                                                                      
    # del memo                                                                  

def random_word():                                                              
    l = int(random.random() * 10)                                               
    w = ''.join([chr(int(random.random() * 26) + ord('a')) for _ in range(l)])  
    return w                                                                    

def main():                                                                     
    t = trie_empty()                                                            
    for _ in range(10000):                                                      
        trie_insert(random_word(), t)                                           

    while True:                                                                 
        fill_dict(t)                                                            

if __name__ == '__main__':                                                      
    main()

运行此命令时,它将继续使用更多的内存,直到我杀死它为止。如果我取消对del memo的注释,则它将在使用恒定内存量的情况下运行。由此得出的结论是,memo返回时未清除局部变量fill_dict

这种行为对我来说真的很神秘,尤其是因为基本上所有上述代码对于查看这种行为都是必需的。即使fill_dict完全未使用参数也不能被忽略,以使程序使用无限制的内存。

这真令人沮丧。当然,现代的,垃圾收集的语言可以清理其自己的变量,而我不必手动删除函数局部变量。函数返回时,甚至C都可以清理堆栈。为什么Python(在这种情况下)不能?

1 个答案:

答案 0 :(得分:4)

我认为这个问题值得一个答案,因为我和程序人以及match mentioned the same starting point in a comment之间的问题已经解决了。

模块级功能fill_dict具有内部功能fill

def fill_dict(root):                                                            
    memo = dict()                                                               
    def fill(pfx='', depth=0):                                                  

此内部名称fill绑定到通过编译其内容创建的实体。该实体指代名称memo,该名称绑定到fill_dict入口处的新的空字典,因此该实体本身就是closure

现在,闭包 可以被垃圾收集了,Python确实有一个垃圾收集器。但是CPython特别有一个两层的收集器:有一个基于引用计数的主要,始终在线的收集器,然后是运行频率不高的真正的标记扫掠式GC。 (请参见When does CPython garbage collect?Why does python use both reference counting and mark-and-sweep for gc?

侧边栏:引用计数收集器怎么了?

参考计数收集器被循环击败:

>>> x = []
>>> x.append(x)
>>> x
[[...]]

此处x绑定到一个列表,该列表的第一个元素是绑定x的列表。也就是说,x [0]是x,x [0] [0]是x,依此类推:

>>> x[0] is x
True
>>> x[0][0] is x
True

在这种这种循环中,删除x无济于事,因为列表是指向自身的。但是,我们可以进行更复杂的循环:

>>> a = dict()
>>> b = dict()
>>> a['link-to-b'] = b
>>> b['link-to-a'] = a
>>> a
{'link-to-b': {'link-to-a': {...}}}
>>> b
{'link-to-a': {'link-to-b': {...}}}

现在,如果我们杀死其中一个链接,圆度将消失:

>>> a['link-to-b'] = None
>>> a
{'link-to-b': None}
>>> b
{'link-to-a': {'link-to-b': None}}

一切都会好起来的。

回到眼前的问题

在这种情况下,fill在其外部memo中引用了fill_dict实例,并且在{{1}中有一个条目 in }是:

memo

变量 memo[pfx] = None, bw 本身是在闭包内部定义的,因此bw指的是闭包(或更准确地说,是指闭包中的实体),而闭包是指memo[pfx] ,这就是我们的循环引用。

因此,即使返回memo,闭包上的引用计数也不会降为零。