Skip to content

列表复制完全指南

在Python中,当你创建一个列表并使用赋值符号 (=) 将它赋给一个变量时,你实际上是在创建一个引用。这意味着,变量指向的是列表在内存中的位置,而不是列表本身。

如果列表中的元素是可变对象,比如列表嵌套列表,那么修改其中一个列表可能会影响到其他列表,这可能会导致一些意想不到的结果。 因此,我们需要学习如何创建列表的副本,这样修改副本就不会影响到原始列表。

让我们通过一个例子来说明这个问题:

python
>>> old_l = [1, 2, 3]

# 将旧列表赋值给新列表
>>> new_l = old_l

# 检查两个列表是否指向同一个内存地址
>>> id(new_l) == id(old_l)
True

# 向新列表添加一个元素
>>> new_l.append(4)
>>> new_l
[1, 2, 3, 4]
>>> old_l
[1, 2, 3, 4]

可以看到,虽然我们只向new_l添加了元素,但是old_l也发生了改变。这是因为new_l = old_l 并没有创建新的列表,而是创建了一个指向old_l所指向的列表的引用。

那么,如何才能真正地复制一个列表呢?Python提供了多种方法:

1. 使用 copy() 方法

copy() 方法会创建一个新的列表对象,并将原始列表中的所有元素复制到新列表中。

python
>>> old_l = [1, 2, 3]

# 使用 copy() 方法复制旧列表
>>> new_l = old_l.copy()

# 检查两个列表是否指向同一个内存地址
>>> id(new_l) == id(old_l)
False

# 向新列表添加一个元素
>>> new_l.append(4)
>>> new_l
[1, 2, 3, 4]
>>> old_l
[1, 2, 3]

现在,当我们修改 new_l 时,old_l 不会受到影响。这是因为 new_l 指向的是一个全新的列表对象。

2. 使用切片 [:]

切片操作符 [:] 可以用来创建一个列表的浅拷贝。 这意味着它会创建一个新的列表,其中包含原始列表所有元素的副本。

python
>>> old_l = [1, 2, 3]

# 使用切片复制旧列表
>>> new_l = old_l[:]

# 检查两个列表是否指向同一个内存地址
>>> id(new_l) == id(old_l)
False

# 向新列表添加一个元素
>>> new_l.append(4)
>>> new_l
[1, 2, 3, 4]
>>> old_l
[1, 2, 3]

切片 [:] 也创建了一个独立的列表副本。

浅拷贝与深拷贝

需要注意的是,copy() 方法和切片 [:] 创建的都是浅拷贝。这意味着,如果原始列表包含可变对象(例如,列表嵌套列表),则副本中的这些可变对象仍然与原始列表中的可变对象共享相同的引用。 如果你想完全复制一个包含可变对象的列表,你需要使用深拷贝,这需要使用 copy 模块中的 deepcopy() 函数。

让我们看一个浅拷贝的例子:

python
>>> old_l = [[1, 2], [3, 4]]
>>> new_l = old_l.copy()

>>> old_l[0].append(5)

>>> old_l
[[1, 2, 5], [3, 4]]
>>> new_l
[[1, 2, 5], [3, 4]]

可以看到,虽然我们只修改了 old_l 中的嵌套列表,但是 new_l 中的嵌套列表也发生了改变。 这是因为 copy() 只是创建了外层列表的副本,而内层列表仍然共享相同的引用。

为了避免这种情况,我们需要使用深拷贝。

3. 使用 copy.deepcopy() 进行深拷贝

copy.deepcopy() 会递归地复制所有对象,包括可变对象,从而创建一个完全独立的副本。

python
import copy

>>> old_l = [[1, 2], [3, 4]]
>>> new_l = copy.deepcopy(old_l)

>>> old_l[0].append(5)

>>> old_l
[[1, 2, 5], [3, 4]]
>>> new_l
[[1, 2], [3, 4]]

现在,当我们修改 old_l 时,new_l 不会受到任何影响,因为 new_l 是一个完全独立的副本。

  • 赋值 = 创建的是引用,而不是副本。
  • copy() 方法和切片 [:] 创建的是浅拷贝。
  • copy.deepcopy() 创建的是深拷贝。

选择哪种复制方法取决于你的具体需求。 如果你的列表只包含不可变对象,那么浅拷贝就足够了。 如果你的列表包含可变对象,并且你需要完全独立的副本,那么你应该使用深拷贝。