Python切片分配内存使用情况

Python Slice Assignment Memory Usage

我在这里读到一条关于栈溢出的注释,在改变列表时执行切片分配更节省内存。例如,

1
a[:] = [i + 6 for i in a]

应该比

1
a = [i + 6 for i in a]

因为前者替换了现有列表中的元素,而后者创建了一个新列表并将a重新绑定到新列表中,从而将旧的a留在内存中,直到可以对其进行垃圾收集。将两者作为速度基准,后者稍快:

1
2
3
4
$ 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首先评估右侧:

An assignment statement evaluates the expression list (remember that this can be a single expression or a comma-separated list, the latter yielding a tuple) and assigns the single resulting object to each of the target lists, from left to right.

在目前的情况下,单个结果对象将是一个新的列表,目标列表中的单个目标将是a[:]

我们可以用生成器表达式替换列表理解:

1
a[:] = (i + 6 for i in a)

现在,右侧的计算结果是生成器而不是列表。基准测试表明,这仍然比幼稚的

1
a = [i + 6 for i in a]

那么生成器表达式是否实际保存了任何内存?乍一看,你可能会认为是这样。但是深入研究函数list_ass_slice()的源代码表明,它没有。这条线

1
v_as_SF = PySequence_Fast(v,"can only assign an iterable");

使用pysequence_fast()首先将iterable(在本例中是生成器)转换为元组,然后将其复制到旧列表中。元组使用与列表相同的内存量,因此使用生成器表达式与在本例中使用列表理解基本相同。在最后一次复制期间,原始列表中的项目将被重用。

从道义上看,最简单的方法在任何方面都是最好的。