我在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
,让匿名列表浮动,直到它被垃圾收集。如果是这种情况,那么前一种方法将使用相同数量的内存,同时也会变慢。
任何人都可以明确地显示(使用基准或官方文档)这两种方法中哪一种更有效,哪种方式更受欢迎?
提前致谢。
答案 0 :(得分:44)
该行
a[:] = [i + 6 for i in a]
不会保存任何内存。 Python确实首先评估右侧,如language documentation:
中所述赋值语句计算表达式列表(请记住,这可以是单个表达式或以逗号分隔的列表,后者产生元组)并将单个结果对象从左到右分配给每个目标列表。
在目前的情况下,单个结果对象将是新列表,目标列表中的单个目标将是a[:]
。
我们可以用生成器表达式替换列表推导:
a[:] = (i + 6 for i in a)
现在,右侧评估生成器而不是列表。基准测试表明,这仍然比天真
慢a = [i + 6 for i in a]
生成器表达式实际上是否保存了任何内存?乍一看,你可能会认为它确实如此。但是深入研究source code of the function list_ass_slice()
表明它没有。这条线
v_as_SF = PySequence_Fast(v, "can only assign an iterable");
使用PySequence_Fast()将iterable(在本例中为生成器)转换为元组,然后将其复制到旧列表中。元组使用与列表相同的内存量,因此使用生成器表达式与在这种情况下使用列表推导基本相同。在最后一次复制期间,重复使用原始列表的项目。
道德似乎是最简单的方法在任何方面都是最好的方法。