在 python 中转换列表的优雅且廉价的方式

Elegant and cheap way to transform lists in python

假设我有一个我想增加的数字列表,我只对增加后的值感兴趣,而不是之后的原始值。 python最就地无需复制列表的方法是什么?

a = [1, 2, 3]
a = [i+1 for i in a]

生成 a 的中间副本,或者 python 解释器是否对此进行了优化?

不幸的是,我的python知识仍然很肤浅。我的母语是C++。

无需复制列表,您可以这样做:

In [1]: a = [1, 2, 3]

In [2]: id(a)
Out[2]: 48701592

In [3]: for i in xrange(len(a)): # range(len(a)) for python 3
   ...:     a[i] += 1
   ...:     

In [4]: a
Out[4]: [2, 3, 4]

In [5]: id(a)
Out[5]: 48701592

要增加列表中的每个元素而不创建列表的另一个副本,您应该遵循@Akavall 的回答。

关于列表理解是否创建列表的中间副本的问题,以下是解释。

列表理解

a = [i+1 for i in a]

可以展开为:

b = []
for i in a:
    b.append(i+1)
a = b

如您所见,我们正在遍历列表并逐个递增每个元素。这些元素存储在一个临时列表中,并将新列表分配回 a.

在这种情况下,a 只是一个引用列表的标签。 a = b 只是将引用更新为指向新列表。

另一个使用 numpy 数组的选项

import numpy as np

A=np.array([1,2,3])
A=A+1

Ans to Q#1 - 一旦我们进一步操作数组(或数组元素),它就像对原始数组的引用,并对其进行持久的新更改。它可以称为 intermediate 副本,但显然不是原始数组的副本。

Ans to Q#2 - Python 内部并未优化 a,如您的问题中所述。

尽管您可以通过以下方式获得所需的结果:

a = [1, 2, 3]
a = [i+1 for i in a]
print a

仍然使用 slicing 符号 : 会更合适:

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

*** 感谢@Paul Rooney 分享示例。

我相信(但我 错了 - 请参阅评论)避免复制列表的正确方法是使用切片赋值和生成器表达式:

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

使用列表推导式[i+1 for i in a]的问题在于它会首先构建一个新列表,然后分片赋值会遍历临时列表以将a的每个元素绑定到价值。

使用生成器表达式,当切片分配请求附加值时,可以延迟迭代现有列表,因此原则上不需要创建临时列表。但是 - 正如我被纠正的那样 - 切片分配导致从生成器表达式创建列表。

列表中的整数存在一个问题:这些实际上是不可变对象,因此如果您增加一个整数,它实际上将被一个新对象替换。

因此,虽然列表理解创建一个中间副本,但开销可能不是什么大问题。如有疑问,您可以使用 timeit.

分析备选方案

如果有其他引用列表,就会出现一个经常被忽视的问题:

>>> a = [1,2,3,4]
>>> b = a
>>> for i in xrange(len(a)):   # range() in Python 3!!
...     a[i] += 1
... 
>>> a
[2, 3, 4, 5]
>>> b
[2, 3, 4, 5]

如果你为a创建一个新列表,b当然不会看到这些修改:

>>> a = [1,2,3,4]
>>> b = a
>>> a = [i + 1 for i in a]
>>> a
[2, 3, 4, 5]
>>> b
[1, 2, 3, 4]

所以,答案实际上是:视情况而定。使用 for 循环