Python中的按值而非引用排序列表

205

让我们来举个例子

a=['help', 'copyright', 'credits', 'license']
b=a
b.append('XYZ')
b
['help', 'copyright', 'credits', 'license', 'XYZ']
a
['help', 'copyright', 'credits', 'license', 'XYZ']
我想在列表'b'中添加值,但是列表'a'的值也发生了改变。 我认为我知道这是为什么(Python通过引用传递列表)。 我的问题是“如何通过按值传递来避免将'b'附加到'a'中并更改其值?”

2
b = a.copy(),这样就可以正常工作了。 - Rezaul Karim Shaon
11个回答

285

在Python中,您无法通过值传递任何东西。如果您想复制a,可以按照官方Python FAQ中所述显式地执行此操作:

b = a[:]

29
对我没用,我在b上做的任何更改都会同时出现在a中。 - Mannix
1
@Mannix 你能发布一篇新问题,其中包含显示问题的完整代码(即应该失败的断言)吗?很可能,你不是修改列表本身,而是它的元素。如果你想要一个同时拷贝其元素的新列表,请创建一个 深层拷贝 - phihag
10
然而,如果a是一个二维列表,这样做将行不通。 - Pythoner
对于二维数组,可以使用 map 函数:old_array = [[2, 3], [4, 5]]

python2.*

new_array = map(list, old_array)

python3.*

new_array = list(map(list, old_array))
- Pythoner
@Pythoner,您所描述的代码适用于二维列表,而不是数组。copy.deepcopy(something) 对二者都有效。但是,如果您的列表是2D或任何数据结构而不是简单列表,则您需要不同的问题。 - phihag
但要知道,在这种情况下,如果您在a中有一个列表,并且如果您在b中更改它,则它也会在a中更改。但整数或字符串不会被更改。这就是多维列表提到的意思。 - Dmitry Polovinkin

165
要复制一个列表,您可以使用list(a)a[:]。在这两种情况下都会创建一个新对象。
然而,对于可变对象的集合,这两种方法都有限制,因为内部对象保持其引用不变:
>>> a = [[1,2],[3],[4]]

>>> b = a[:]
>>> c = list(a)

>>> c[0].append(9)

>>> a
[[1, 2, 9], [3], [4]]
>>> c
[[1, 2, 9], [3], [4]]
>>> b
[[1, 2, 9], [3], [4]]
>>> 

如果您想要完整复制对象,您需要使用copy.deepcopy

>>> from copy import deepcopy
>>> a = [[1,2],[3],[4]]

>>> b = a[:]
>>> c = deepcopy(a)

>>> c[0].append(9)

>>> a
[[1, 2], [3], [4]]
>>> b
[[1, 2], [3], [4]]
>>> c
[[1, 2, 9], [3], [4]]
>>> 

什么是普通复制和深度复制的区别? 为什么会发生上述情况?我认为我有一个大致的理解,似乎与第二层遇到的问题相同。它的内部工作原理是什么? - AsheKetchum

43

就性能而言,我最喜欢的答案是:

b.extend(a)

比较相关替代品在性能方面的差异:

In [1]: import timeit

In [2]: timeit.timeit('b.extend(a)', setup='b=[];a=range(0,10)', number=100000000)
Out[2]: 9.623248100280762

In [3]: timeit.timeit('b = a[:]', setup='b=[];a=range(0,10)', number=100000000)
Out[3]: 10.84756088256836

In [4]: timeit.timeit('b = list(a)', setup='b=[];a=range(0,10)', number=100000000)
Out[4]: 21.46313500404358

In [5]: timeit.timeit('b = [elem for elem in a]', setup='b=[];a=range(0,10)', number=100000000)
Out[5]: 66.99795293807983

In [6]: timeit.timeit('for elem in a: b.append(elem)', setup='b=[];a=range(0,10)', number=100000000)
Out[6]: 67.9775960445404

In [7]: timeit.timeit('b = deepcopy(a)', setup='from copy import deepcopy; b=[];a=range(0,10)', number=100000000)
Out[7]: 1216.1108016967773

1
谢谢您将性能纳入讨论,这帮助我决定使用哪种方法。 - Kyle Pittman
1
我刚刚看到了你的回答,非常感谢你提供这样高质量的答复!在讨论Python时,通常不会考虑性能问题,但对于大型数据集来说,这确实很重要。 - Artur
1
我喜欢这个答案,但它与列表的值无关。正如Jordan Pagni所提到的,如果您的列表是多维的,即列表中包含列表(以及更多),那么唯一可行的解决方案就是需要最长时间的解决方案:b = deepcopy(a)。 - Eran Yogev
@EranYogev,我同意。但是如果你想要实现性能,你必须对列表的内容有一些了解。:( - Kyr
8
extend() 方法的测试用例与其他方法不可比较。要使用 extend() 方法,首先必须创建一个数组,而其他构造函数将为您创建数组。因此,通过跳过列表对象的初始化,您有效地使 extend() 方法具有优势。要纠正测试,请将 b = [] 从设置中移动到测试下的语句中,例如 b = []; b.extend(a)。这将有利于使用切片创建副本的第二种情况。 - Mr. Deathless
1
为什么 b=list(a) 的执行时间是 b=a[:] 的两倍? - scherm

19

另外,你可以这样做:

b = list(a)

这将适用于任何序列,甚至那些不支持索引和切片的序列...


请注意,这种方法也无法完全处理多维列表 - 因此,如果您在原始列表中有一个列表副本,如果更改了其中一个副本,它将在所有位置上进行更改。 - Dmitry Polovinkin

17

如果你想复制一个一维列表,请使用

b = a[:]

然而,如果a是一个二维列表,那么这种方法不会适用于你。也就是说,对a所做的任何更改都将反映在b中。在这种情况下,请使用

b = [[a[x][y] for y in range(len(a[0]))] for x in range(len(a))]

并不是说任何更改都会被反映出来 - 只有当原始列表中的列表被更改时,它才会反映在副本上。其他数据更改将不会反映在其他副本上,因此可以放心更改字符串或整数。 - Dmitry Polovinkin

16

正如phihag在他的回答中提到的那样,

b = a[:]

由于对列表进行切片会创建一个新的内存id(这意味着您不再引用内存中的同一对象,您对其中一个所做的更改将不会反映在另一个上),因此切片对您的情况有效。

然而,存在一个小问题。如果您的列表是多维的,即列表内含列表,则仅仅进行切片是无法解决这个问题的。在更高的维度中进行更改,即原始列表中的列表,会在两者之间共享。

不要担心,有一个解决方案。模块copy具有一个很棒的复制技术,可以解决这个问题。

from copy import deepcopy

b = deepcopy(a)

无论列表包含多少级别,都会复制一个带有新内存 ID 的列表!


很好的回答,Jordan!谢谢!!! 你知道这个的原因吗? - Felipe
非常好的答案,特别是因为它提到了初始解决方案将失败的情况(嵌套对象,其他对象列表)和解决方案(deepcopy())。 - AruniRC

6
当你执行b = a时,你只是简单地创建了指向a相同内存的另一个指针, 这就是为什么当你追加到b时,a也会改变。
你需要创建a副本,可以像这样完成:
b = a[:]

1
只是技术上的细节,但 Python 变量并不是真正的指针。更准确地说,当你执行 b = a 时,你创建了另一个引用指向 a 引用的列表对象。 - Endyd

5
创建列表的副本请执行以下步骤:
b = a[:]

3

我发现我们可以使用extend()函数来实现copy()函数的功能。

a=['help', 'copyright', 'credits', 'license']
b = []
b.extend(a)
b.append("XYZ") 

2
我建议采用以下解决方案:
b = []
b[:] = a

这将复制a中的所有元素到b中。复制将是值复制,而不是引用复制。

这并不比 b = a[:] 更好。 - Endyd

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接