对象拷贝的方法具有多种,例如简单的赋值、切片、工厂(list()、dict())等,不同的方法所拷贝的内容是不同的。所以需要格外区分一下各种拷贝方法。以进行更加可靠的编程,避免不必要的错误。
一 对象赋值
对象赋值实际是简单的对象引用。即当创建了一个对象,把该对象赋给另一个变量时,Python并没有重新创建一个新的一模一样的对象,而只是让该变量指向该对象(也可以说,拷贝了这个对象的引用)。
如下例所示,其中id()返回对象的内存地址。下例表明新变量y和原变量x指向的是同一块内存地址,即两者指向同一个对象。这表明,赋值时仅拷贝了对象的引用(即存储对象地址的变量)。
>>> x = 3.14
>>> y = x
>>> print([id(item) for item in [x,y]])
[1904199604888, 1904199604888]
下面的例子从另一个角度解释了这个问题。即,对person1修改信息后,person2的信息也发生了同样的变化。这也说明,两者指向的是同一个对象。
>>> person = ['name','Tel']
>>> person1 = person
>>> person2 = person
>>> person1[0] = 'joe'
>>> person1[1] = '13081851234'
>>> person1
['joe', '13081851234']
>>> person2
['joe', '13081851234']
下面的输出表示,赋值得到的新对象与原对象,不止对象相同(内存地址相同),内容也相同(内存地址相同)。
换句话说,组合对象相同,子对象也相同。(相同不止是说值相同,而是指内存地址相同,两者就是同一个)
>>> id(person1)
1904231981896
>>> id(person2)
1904231981896
>>> [x for x in person1]
['joe', '13081851234']
>>> [id(x) for x in person1]
[1904241329072, 1904241294960]
>>> [id(x) for x in person2]
[1904241329072, 1904241294960]
对象赋值的特点:拷贝生成的对象与原对象,两对象是相同的(两个对象的内存地址相同),内容也是相同的。
二 浅拷贝注意:浅拷贝和深拷贝仅仅是对组合对象(或说容器类型)而言,所谓的组合对象(容器类型)就是包含了其他对象的对象,比如列表、类实例。而对于非容器类型,比如数字、字符串以及其他“原子”类型,没有被拷贝一说,产生的都是原对象的引用。
浅拷贝,是指创建一个新的对象,其内容是原对象中元素的引用。(拷贝的组合对象是新的,内容不是。换句话说,拷贝组合对象,不拷贝子对象)
从下面的例子可以看出,对a进行浅拷贝得到b,a和b指向内存中不同的 list对象,但他们的元素却指向相同的int对象(子对象)。
>>> a = [1,2,3]
>>> b = list(a)
>>> id(a)
1985581168840
>>> id(b)
1985580873352
>>> for x,y in zip(a,b):
print(id(x),id(y))
1509869232 1509869232
1509869264 1509869264
1509869296 1509869296
常见的浅拷贝操作有:切片操作、工厂函数、对象的copy()方法、copy模块中的copy()函数。
使用示例如下:
浅拷贝的特点:拷贝生成的对象与原对象,两对象是不同的(内存地址不同),内容相同。
三 深拷贝
深拷贝,是指创建一个新的对象,然后递归拷贝原对象所包含的子对象。深拷贝只有一种方式:copy模块的deepcopy函数。
从下例可以看出浅拷贝和深拷贝的区别:
>>> import copy
>>> a = [[1,2],[3,4],[5,6]]
>>> b = copy.copy(a) #浅拷贝得到b
>>> c = copy.deepcopy(a) #深拷贝得到c
>>> print(id(a),id(b)) #a和b不同
1985581099016 1985581097992
>>> for x,y in zip(a,b): #a和b的子对象相同
print(id(x),id(y))
1985539661512 1985539661512
1985581168520 1985581168520
1985581168456 1985581168456
>>> print(id(a),id(c)) #a和c不同
1985581099016 1985581168392
>>> for x,y in zip(a,c):#a和c的子对象也不同
print(id(x),id(y))
1985539661512 1985581097736
1985581168520 1985581098056
1985581168456 1985581098184
上例中容器对象的内容是可变对象(列表list),如果容器对象的内容是不可变对象呢?(可变对象:list、dict。不可变对象:int string float tuple)
>>> import copy
>>> a = [1,2,3]
>>> b = copy.deepcopy(a)
>>> print(id(a),id(b)) #组合对象不同(内存地址不同)
1845321737608 1845321799688
>>> for x,y in zip(a,b): #内容相同
print(id(x),id(y))
1509869232 1509869232
1509869264 1509869264
1509869296 1509869296
>>> b[0] = 10
>>> b
[10, 2, 3]
>>> for x,y in zip(a,b): #改变了b[0],然而a[0]并没有随之变化
print(id(x),id(y))
1509869232 1509869520
1509869264 1509869264
1509869296 1509869296
为什么上例中组合对象a和b的内容会相同呢?为什么改变b[0]后,a[0]没有随着变化呢?
皆因为组合对象的内容是不可变对象int。
因为内容是不可变对象,当需要一个新对象时,Python会返回已经存在的某个类型、值都与原对象相同的对象(在这里就是a的子对象了)的引用。这种机制并不会影响a和b的相互独立性,因为当两个对象指向同一个不可变对象时,对其中一个赋值不会影响到另外一个。
当对b[0]赋值时,b[0]是不可变对象,所以首先另开辟一块内存空间,创建一个新的对象并进行初始化,然后令b[0]指向该新对象(改变b[0]中存储的(引用)内存地址)。
总结:
1、赋值:简单地拷贝对象的引用,两个对象的id相同。
2、浅拷贝:创建一个新的组合对象,这个新对象与原对象共享内存中的子对象。
3、深拷贝:创建一个新的组合对象,同时递归地拷贝所有子对象,新的组合对象与原对象没有任何关联。虽然实际上会共享不可变的子对象,但不影响它们的相互独立性。
参考网址与书籍:
《Python核心编程(第二版)》