浅复制
复制列表(或其他可变类型)最简单的办法是使用内置的类型构造方法(还可以用 copy 内置库的 copy() 方法)例如:
l1 = [3, [66, 55, 44], (7, 8, 9)]
l2 = list(l1)
print(l1 == l2) # True
print(l1 is l2) # False
然而,使用构造方法或 cpoy.copy() 做的都是浅复制(即复制了最外层容器,副本中的元素还是源容器中的元素的引用,见上图)。如果所有的元素都是不可变的,那么这样做没有问题,还能节省内存,但是如果又可变类型的元素,可能会导致意想不到的问题。
例如下面的例子,可以根据注释和内存示意图结合起来一起理解:
# 参考图一,复制列表
l1 = [3, [66, 55, 44], (7, 8, 9)]
l2 = list(l1)
# 参考图二,l1 添加元素 100,l1[1] 移除元素 50,因为 l1[1] 是可变类型引用,
# 所以 l2[1] 和 l1[1] 都指向用一个对象,l1[1] 修改之后 l2 的值也会变化;
l1.append(100)
l1[1].remove(55)
print('l1:', l1) # l1: [3, [66, 44], (7, 8, 9), 100]
print('l2:', l2) # l2: [3, [66, 44], (7, 8, 9)]
# 参考图三,l2 添加元素,l2[1] 修改会同步修改 l1,但是注意,l2[2] 因为引用的是不可变类型,
# 所以当执行 l2[2] += (10, 11) 时不会修改源容器,而是会新建一个元组容器,所以 l2[2] 的值改变而 l1[2] 的值不变;
l2[1] += [33, 22]
l2[2] += (10, 11)
print('l1:', l1) # l1: [3, [66, 44, 33, 22], (7, 8, 9), 100]
print('l2:', l2) # l2: [3, [66, 44, 33, 22], (7, 8, 9, 10, 11)]
图一: 图二: 图三:
深复制
浅复制没什么问题,但有时我们需要的是深复制(即副本不共享内部对象的引用),我们可以用 copy 模块提供的 deepcopy() 函数为任何对象做深复制。
为了演示 dopy() 和 deepcopy() 的用法,下面示例定义了一个简单的类,用这个类表示运载乘客的校车,在途中会有乘客上下车:
class Bus:
def __init__(self, passengers=None):
if passengers is None:
self.passengers = []
else:
self.passengers = list(passengers)
def pick(self, name):
self.passengers.append(name)
def drop(self, name):
self.passengers.remove(name)
# 创建实例
bus1 = Bus(['A', 'B', 'C', 'D'])
# 浅复制
bus2 = copy.copy(bus1)
# 深复制
bus3 = copy.deepcopy(bus1)
# 变量各自指向不同的对象
print(id(bus1), id(bus2), id(bus3)) # 4461920832 4461921056 4461922176
# 修改 bus1 乘客,发现 bus2 同步发生变化,bus3 没有变化
bus1.drop('B')
print(bus2.passengers) # ['A', 'C', 'D']
print(bus3.passengers) # ['A', 'B', 'C', 'D']
# 实际上 bus1 和 bus2 的 passengers 属性都是指向同一个对象,bus 3是单独的一个对象
print(id(bus1.passengers), id(bus2.passengers), id(bus3.passengers))
内存示意图:
此外,有时候深复制可能会太深了,例如对象可能会引用不该复制的外部资源和单例值,我们可以实现特殊方法 __copy__ 和 __deepcopy__ 来控制 copy 和 deepcopy 的行为。