在 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 循环
假设我有一个我想增加的数字列表,我只对增加后的值感兴趣,而不是之后的原始值。 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 循环