python 中 深拷贝和浅拷贝的理解
在总结 python 对象和引用的时候,想到其实 对于python的深拷贝和浅拷贝也可以很好对其的进行理解。
在python中,对象的赋值的其实就是对象的引用。也就是说,当创建一个对象,然后赋给另外一个变量之后,实际上只是拷贝了这个对象的引用。
我们先用 利用切片操作和工厂方法list方法 来阐述一下浅拷贝。
举个栗子:
Tom = ['Tom', ['age', 10]] Jack = Tom[:] ……切片操作 June = list(Tom)
接下来查看一下 上述三个变量的引用:
>>> id(Tom) 140466700697824 >>> id(Jack) 140466700700488 >>> id(June) 140466700770192
可以发现,三个变量分别指向了不同的对象。我们再来看一下这三个对象的内容:
>>> Tom ['Tom', ['age', 10]] >>> Jack ['Tom', ['age', 10]] >>> June ['Tom', ['age', 10]]
显然这三个对象的内容会是一样的,因为通过上面的 切片操作 以及 工厂函数list 对Tom引用的对象进行了拷贝。接下来再进行进一步验证:
我们对 Jack 和 June 引用的对象进行修改:
>>> Jack[0] = 'Jack' >>> Jack ['Jack', ['age', 10]] >>> June[0] = 'June' >>> June ['June', ['age', 10]]
现在我们打算对Jack的年龄进行修改:
>>> Jack[1][1] = 20 >>> Jack ['Jack', ['age', 20]]
可以看到Jack年龄变为了20; 让我们接下来看一下Tom, June的年龄:
>>> print Tom, Jack, June ['Tom', ['age', 20]] ['Jack', ['age', 20]] ['June', ['age', 20]]
奇怪的事情发生了,我们仅仅是修改了 Jack的年龄, 但是Tom 和 June 的年龄跟着改变了, 这是为什么呢?
这个就涉及到了python中浅拷贝的奥秘:
我们先来看一下上面 Tom, Jack, June中内部元素的 内存地址:
>>> for x in Tom: ... print id(x) ... 140704715293600 --> 'Tom' 140704715147816 --> ['age', 20] >>> for x in Jack: ... print id(x) ... 140704715286256 --> 'Jack' 140704715147816 --> ['age', 20] >>> for x in June: ... print id(x) ... 140704715286352 -->'June' 140704715147816 -->['age', 20]
仔细观察可以看到,Tom, Jack, June 三个变量的 岁数元素['age', 20] 指向同一个 对象; 那为什么他们的 名字元素 分别指向不同的对象。这是因为,在python中的分为 可变数据对象(列表,字典) 和 不可变数据对象(整型,字符串,浮点型,元祖)。 正是因为这个原因,他们的 名字元素 为字符串,为不可变数据对象,因此开始为 Jack 和 June 重新命名的时候,实际上内存中已经创建了 Jack 和 June对象。而 岁数元素 是 可变数据对象,所以并不会在内存中创建新的对象,Tom,Jack,June的岁数元素都引用同一个对象,导致修改其中一个会让另外俩个的年龄跟着变化。
这个就是python的浅拷贝,其仅仅是拷贝了 一个整体的对象(应该说一个对象最外面的那一层),而对于对象里面包含的元素不会进行拷贝。
接下来,我们 利用copy中的deepcopy方法 来阐述一下 深拷贝:
还是用上面那个栗子:
为了让 Tom, Jack, June之间互不影响,我们用deepcopy方法对Tom进行拷贝生成 Jack 和 June:
>>> Tom = ['Tom', ['age', 10]] >>> import copy >>> Jack = copy.deepcopy(Tom) >>> June = copy.deepcopy(Tom) >>> Jack ['Tom', ['age', 10]] >>> June ['Tom', ['age', 10]] >>> Tom ['Tom', ['age', 10]]
让我们看一下Tom, Jack, June分别指向的内存地址:
>>> print id(Tom), id(June), id(Jack) 140707738759392 140707738799280 140707738797192
三个内存地址不同,然后我们接着改变Jack 和 June的名字,并查看修改后它们的内部元素所指向的内存地址:
>>> Jack[0] = 'Jack' >>> June[0] = 'June' >>> Tom ['Tom', ['age', 10]] >>> Jack ['Jack', ['age', 10]] >>> June ['June', ['age', 10]] >>> for x in Tom: ... print id(x) ... 140707738882976 --> 'Tom' 140707738737192 --> ['age', 10] >>> for x in Jack: ... print id(x) ... 140707738875584 --> 'Jack' 140707738910016 --> ['age', 10] >>> for x in June: ... print id(x) ... 140707738876640 -->'June' 140707738910160 --> ['age', 10]
可以清楚的看到,他们的内部元素也指向了不同的对象,说明通过deepcopy方法,对元素进行了彻底的拷贝(包括内部元素)。
最后总结一下思路:
思路一:利用切片操作和工厂方法list方法拷贝就叫浅拷贝,只是拷贝了最外围的对象本身,内部的元素都只是拷贝了一个引用而已。
思路二:利用copy中的deepcopy方法进行拷贝就叫做深拷贝,外围和内部元素都进行了拷贝对象本身,而不是引用。
但是对于数字,字符串和其他原子类型对象等,没有被拷贝的说法,即便是用深拷贝,查看id的话也是一样的,如果对其重新赋值,也只是新创建一个对象,替换掉旧的而已。