考虑以下代码:
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(在这种情况下)不能?
答案 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
,闭包上的引用计数也不会降为零。