Python切片赋值内存使用情况
我在Stack Overflow的一个评论中看到,改变列表时用切片赋值会更节省内存。比如说,
a[:] = [i + 6 for i in a]
这种方式应该比
a = [i + 6 for i in a]
更节省内存,因为前者是在原有列表中替换元素,而后者是创建一个新列表,然后把变量a
指向这个新列表,这样旧的a
就会留在内存中,直到被垃圾回收。测试这两种方法的速度时,后者稍微快一点:
$ python -mtimeit -s 'a = [1, 2, 3]' 'a[:] = [i + 6 for i in a]'
1000000 loops, best of 3: 1.53 usec per loop
$ python -mtimeit -s 'a = [1, 2, 3]' 'a = [i + 6 for i in a]'
1000000 loops, best of 3: 1.37 usec per loop
这我可以理解,因为重新绑定一个变量应该比在列表中替换元素要快。不过,我找不到任何官方文档来支持这个内存使用的说法,也不太确定该怎么测试这个。
从表面上看,内存使用的说法对我来说是有道理的。不过,仔细想想,我觉得在前一种方法中,解释器可能会先从列表推导中创建一个新列表,然后再把那个列表的值复制到a
中,这样那个匿名列表就会在内存中漂浮,直到被垃圾回收。如果真是这样的话,前一种方法的内存使用量就和后者一样,但速度却更慢。
有没有人能明确地(通过基准测试或官方文档)展示这两种方法中哪种更节省内存/哪种是更推荐的方法?
提前谢谢大家。
1 个回答
这一行
a[:] = [i + 6 for i in a]
其实不会节省任何内存。Python 确实会先计算右边的内容,正如官方文档所说:
赋值语句会先计算表达式列表(记住,这可以是一个单独的表达式,也可以是用逗号分隔的多个表达式,后者会生成一个元组),然后将得到的单一结果对象从左到右赋值给每个目标列表。
在这个例子中,得到的结果是一个新的列表,而目标列表中的唯一目标就是 a[:]
。
我们可以用生成器表达式来替代列表推导式:
a[:] = (i + 6 for i in a)
现在,右边的结果变成了一个生成器,而不是一个列表。基准测试显示,这样做的速度仍然比简单的
a = [i + 6 for i in a]
要慢。那么,生成器表达式真的能节省内存吗?乍一看,你可能会觉得可以。但深入查看函数 list_ass_slice()
的源代码会发现,实际上并没有。那一行
v_as_SF = PySequence_Fast(v, "can only assign an iterable");
使用了PySequence_Fast(),先把可迭代对象(在这个例子中是生成器)转换成一个元组,然后再复制到旧列表中。元组和列表占用的内存是一样的,所以在这种情况下,使用生成器表达式和使用列表推导式基本上是一样的。在最后的复制过程中,原列表的项被重新利用了。
这个例子的教训似乎是,最简单的方法在各方面都是最好的选择。