Python 3.x中动态视图对象的范围

3
在 Python 中,当原始对象被删除后,视图对象会发生什么?例如,
a = {'foo': 1 , 'bar': 2, 'baz': 3 }
b = a.keys()   # => dict_keys(['bar', 'foo', 'baz'])

现在,如果对字典进行任何更改,这些更改都会反映在b中。例如,

a['qux'] = 4
print(b)  # => dict_keys(['bar', 'qux', 'foo', 'baz'])

然而,当字典被删除时,动态变量仍包含已删除字典中所有键的值。

del a
a        # NameError: name 'a' is not defined
print(b) # => dict_keys(['bar', 'qux', 'foo', 'baz']) 

问题

本质上,我想知道在删除字典后,是否必须始终确保删除具有键值的任何变量。如果字典很大,这样做可能会导致内存泄漏吗? 感谢您的反馈。

附注:

是的,我知道我可以将键放入列表中:

c = list(a.keys())

但是我使用视图对象,因为它们比列表具有更小的内存占用。


2
变量名 a 被删除了,但是变量名 b 仍然持有对实际对象即字典键的引用,因此该对象无法被删除。 - Padraic Cunningham
好的,您也可以执行 b = a; del a,尽管 a 已被删除,但 b 仍然存在。 - Remi Guan
@KevinGuan,因为你正在引用指向a对象的指针,而不是名称a。 - Padraic Cunningham
3
注意:在Python中,明确调用del是相当罕见的——也许最常见的有效使用方式是通过索引从集合中删除项目,例如 del foo[key]。不应将 del a 解释为“删除名为a的对象”,而应该解释为“删除名称a”;也就是说,del只是解除名称与对象之间的绑定关系。如果指向a曾经指向的对象存在其他名称或引用,则该对象将继续存在于内存中。否则,内存将被释放。重要的是,如果您在代码中频繁使用del(可能并非如此),那么这可能是不必要的。 - jme
1
http://nedbatchelder.com/text/names.html - Padraic Cunningham
1
附注:del不是函数,而是关键字语句。你不需要(通常也不应该)在它后面加括号,就像你在return后面不用加括号一样。 - ShadowRanger
2个回答

4
我建议不要使用术语"变量"。最好使用"允许访问对象的名称"。对于你的例子,a和b是字典对象和字典键的名称。在Python 3中,dict.keys()会给你一个反映底层字典变化的键视图对象。因此,键视图对象保留对字典的引用。
所以你不是删除字典,而是删除指向它的名称。只有当没有更多名称(对字典的引用)时,垃圾收集器才会删除字典。
如果你以结构化方式编写函数,这些函数不会对全局对象进行操作,内存泄漏就比较少见。实际上,del通常很少使用。

0

del 只是在 Python 内部将对象的引用计数减少。字典本身将在程序中没有活动引用时从内存中释放 (disposed) 远晚于此时,

当您将 keys() 的结果保存在变量中时,除了字典本身的变量之外,还会增加字典的引用计数。只要任何引用仍然存在,变量就不会被释放。

我可以用非常简单但完全相同的结构来表达您的代码:

a = dict()
b = a # reference only
del(b)
# dict is not garbage-collected, as a still holds the reference

a != None # True

2
在CPython(参考解释器)中,如果引用计数实际上降至零,则对象会立即被处理,而不是稍后,更不用说“很久以后”。您帖子的其余部分是正确的,但如果引用计数降至零(包括隐含地持有的所有引用),则该对象将立即消失。唯一需要进行非确定性清理的时间是存在引用循环的情况下; 周期性垃圾收集器会间歇性运行,因此未引用的循环可能需要一段时间才能清除。 - ShadowRanger
感谢您的纠正。虽然我谈论的是实践而不是干净的理论。因此,对象通常彼此依赖;在真实的应用程序中释放它们需要时间,而应用程序则会传递到另一个前台任务。当我在C中使用Python时,我对此有了更深入的了解。但是,这是在早期的3.x版本中发生的,让人头痛。 - Croll

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接