python 中 深拷贝和浅拷贝的理解

时间:2021-12-15 20:34:05

在总结 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的话也是一样的,如果对其重新赋值,也只是新创建一个对象,替换掉旧的而已。