“del”到底是做啥的?
Posted
技术标签:
【中文标题】“del”到底是做啥的?【英文标题】:What does "del" do exactly?“del”到底是做什么的? 【发布时间】:2014-01-29 23:04:06 【问题描述】:这是我的代码:
from memory_profiler import profile
@profile
def mess_with_memory():
huge_list = range(20000000)
del huge_list
print "why this kolaveri di?"
这是我从解释器运行时的输出:
Line # Mem 使用增量行内容
3 7.0 MiB 0.0 MiB @profile
4 def mess_with_memory():
5
6 628.5 MiB 621.5 MiB huge_list = range(20000000)
7 476.0 MiB -152.6 MiB del huge_list
8 476.0 MiB 0.0 MiB print "why this kolaveri di"
如果您注意到输出,创建巨大的列表消耗了 621.5 MB,而删除它只释放了 152.6 MB。当我检查docs时,我发现了以下语句:
the statement del x removes the binding of x from the namespace referenced by the local scope
所以我猜,它并没有删除对象本身,而是解除绑定。 但是,它在解除绑定时做了什么释放了这么多空间(152.6 MB)。有人可以痛苦地解释一下这里发生了什么吗?
【问题讨论】:
del huge_list
和 huge_list = None
[大致] 等价于讨论对象可达性。
您是否真的遇到过程序最终空间不足并引发MemoryError
的问题,或者您的计算机是否会陷入交换颠簸的地狱?如果没有可见的问题,实际上可能没有值得担心的问题。
@abarnert:是的,它只是为了“提高我对 python 的理解”的目的。
152.6 MIB 几乎正好是每个列表元素 8 个字节。似乎在理性的范围内。我更想知道是什么占用了其他 469 MiB。
余数是每个元素 24 字节加上一点 slop,而 24 字节恰好是 64 位 CPython 2.7 的默认构建中的 PyInt
标头的大小,所以……这是可能的PyInt
的大部分或全部内存都位于某个级别或另一个级别的空闲列表中,而 PyList
的内部存储缓冲区(价值 152MiB 的指向那些 PyInt 对象的指针)被回收,因为它是一个巨大的分配(甚至可能直接在单个 mmap
或 VirtualAlloc
调用中分配)而不是一堆小东西。
【参考方案1】:
Python 是一种垃圾收集语言。如果某个值不再从您的代码中“可访问”,它最终将被删除。
如您所见,del
语句删除了变量的绑定。变量不是值,它们只是值的名称。
如果该变量是任何地方对该值的唯一引用,则该值最终将被删除。特别是在 CPython 中,垃圾收集器建立在引用计数之上。因此,“最终”意味着“立即”。*在其他实现中,通常是“很快”。
但是,如果存在对相同值的其他引用,则仅删除其中一个引用(无论是 del x
、x = None
、退出 x
存在的范围等)不会清除任何内容.**
这里还有一个问题。我不知道memory_profiler
模块(大概是this one)实际测量的是什么,但描述(谈论psutil
的使用)听起来像是从“外部”测量你的内存使用情况。
当 Python 释放存储空间时,它并不总是——甚至通常——将其返回给操作系统。它在多个级别上保留“空闲列表”,因此它可以更快地重新使用内存,而不是必须一直回到操作系统来请求更多。在现代系统上,这几乎不是问题——如果你再次需要存储,你有它很好;如果你不这样做,它会在其他人需要它时立即被调出,并且永远不会被调入,所以几乎没有伤害。
(最重要的是,我在上面所说的“操作系统”实际上是一个由多个级别组成的抽象,从malloc
库到核心 C 库到内核/分页器,至少有一个这些级别通常都有自己的空闲列表。)
如果您想从内部角度跟踪内存使用情况……嗯,这很难。由于新的tracemalloc
模块,它在 Python 3.4 中变得容易多了。有各种第三方模块(例如,heapy
/guppy
、Pympler
、meliae
)试图获取与早期版本相同的信息,但这很困难,因为从各种分配器获取信息,并将这些信息与垃圾收集器联系起来,在 PEP 445 之前非常困难。
* 在某些情况下,存在 对值的引用……但仅来自其他本身无法访问的引用,可能在一个循环中。就垃圾收集器而言,这仍然算作“无法访问”,但就引用计数而言,则不算。因此,CPython 也有一个“循环检测器”,它每隔一段时间就会运行一次,并找到相互可到达但无法从其他任何人访问的值的循环并清理它们。
** 如果您在交互式控制台中进行测试,可能会有对您的值的隐藏引用难以跟踪,因此您可能认为您已经摆脱了最后一个引用你没有。在脚本中,应该总是可能,即使不是容易,也能解决问题。 gc
模块可以提供帮助,调试器也可以。但当然,它们都也为您提供了添加其他隐藏引用的新方法。
【讨论】:
无法访问?参考?您总是可以尝试*通过 id 恢复流放的变量! ***.com/a/15012814/194586(*可能会或可能不会导致段错误) @NickT 这些变量并没有“死”..在任何情况下,“可能导致段错误”这一事实意味着对象可以被回收(并且因此,无法通过 GC 根访问;该帖子仅显示可以通过 不透明标识符 获取对象(如果它们仍然存在)。 @user2864740:没错。这被记录为不起作用;事实上,它有时会起作用,有时会发生段错误,有时会在半小时后导致段错误,因为你破坏了某些东西,有时只是默默地给你错误的值,只有当你真的扩展定义时才算作“工作”......跨度> @AshwiniChaudhary:在 2.x 中,除了 small bit that's relevant to writing C extensions,它仅记录在源代码中,主要在obmalloc.c
中。
@abarnert:谢谢。 gc 模块确实有帮助。执行 gc.collect() 释放了剩余的内存:)以上是关于“del”到底是做啥的?的主要内容,如果未能解决你的问题,请参考以下文章
FragmentManager 和 FragmentTransaction 到底是做啥的?