在Python中,赋值语句的意义是在对象与变量之间建立引用关系,相当于给对象起了名字。对于复合类型对象,由于其可以保存其他类型对象的引用,而在复制时即引出了“仅复制引用关系”或“同时复制引用对象”两种选择,即浅复制和深复制。
一、浅复制示例
Python中的赋值语句是建立对象与变量名的引用关系,下面语句即是将list1与列表对象建立引用关系:
list1 = ['abc']
而对于集合类型的对象,其中元素保存的可能又是到其他对象的引用,下面语句中,list1保存了3个对象,其中列表和元组是引用,指向其实际对象:
list1 = ['abc', [1,2], (3,4)]
对于list1,当对其进行复制时,就有了浅复制和深复制两种选择。默认的构造函数或切片采用的都是浅复制。下面代码中,list2是list1的浅复制副本(副本共享内部对象的引用):
list1 = ['abc', [1,2], (3,4)]
list2 = list1[:] 或list2 = list(list1)
改变list2中列表的元素,list1中的列表也改变了,可以证明它们引用的是相同的对象:
list2[1].append(3)
list1[1]
对象引用关系如下:
二、深复制示例
有时我们需要对象的深复制,即集合内部对象不共享引用。利用copy模块的deepcopy()函数可以为任意对象做深复制(其还有个copy()函数可以做任意对象的浅复制)。
下面是list1的深复制引用关系图,从结果来看,list1和list2中的列表已经独立复制了,为什么元组还是指向同一个对象?这其实是python内部的一个优化机制:
import copy
list1 = ['abc', [1,2], (3,4)]
list2 = copy.deepcopy(list1)
我们先测试一下变更list2中的列表和元组对象,可以发现list1和list2已经互不影响了:
list2[1].append(3)
list2[2] += (5,6)
2.1 python对不可变对象的优化
上面的例子中,我们对list2[2]的元组对象做+=操作,这个应该做原地变更,但由于元组是不可变对象,所以python会重新创建一个元组,然后重新绑定至list2[2]的引用上,从结果上来看,深复制对于不可以用对象依然复制引用关系并不会造成复制对象间的相互影响。
在深复制时,我们期望的是python会复制所有引用对象,而不仅仅是引用关系,但它会欺骗你。对于不可变对象python依然会复制引用关系,这其实是一种优化,防止重复创建对象。这在普通的常量上也可以体现,下面示例创建了2个变量a,b都等于1,但实际上python只创建了1个数字对象,然后让a,b都指向它(通过id()函数和is可以确定a,b指向的是同一个对象):
a = 1
b = 1
id(a), id(b)
a is b
可以总结出:浅复制可能会造成复制对象间的相互影响,有时你可以利用这种影响传递变化,但有时也可能造成意想不到的影响,特别是在类编写中,尽量避免用可变对象作为初始化参数,其可能造成实例之间的互相干扰。