从底层剖析Python深浅拷贝(超详细)

Posted vladimir

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从底层剖析Python深浅拷贝(超详细)相关的知识,希望对你有一定的参考价值。

目录导航

从底层剖析Python深浅拷贝(超详细)

拷贝的用途

  拷贝就是copy,目的在于复制出一份一模一样的数据。使用相同的算法对于产生的数据有多种截然不同的用途时就可以使用copy技术,将copy出的各种副本去做各种不同的操作。

  值得一提的是绝大部分编程语言中对于copy都有深浅拷贝的概念,所以充分的理解本章节的知识也是在为今后学习其他编程语言少走弯路。

Python =  赋值示例

  好了,废话不多说。直接进入主题,上代码:

>>> li1 = ["a","b",[1,2]] # 注意存储的数据类型。第一层存储2个不可变类型,1个可变类型小容器(list),第二层存储2个不可变类型
>>> id(li1)  # 第一层,查看变量名所指向的列表(第一层容器)的内存地址id号
2324901133120
>>> id(li1[0])  # 第一层,查看第一层容器中的具体元素内存地址id号  
2324900663472
>>> id(li1[-1])  # 第二层(小容器id号),查看第一层容器中的小容器list的内存地址id号
2324900812864
>>> li1
[a, b, [1, 2]]
>>> #=============手动分割线=============
>>> li2 = li1
>>> id(li2)  # 第一层,查看变量名所指向的列表(第一层容器)的内存地址id号
2324901133120
>>> id(li2[0])  # 第一层,查看第一层容器中的具体元素内存地址id号  
2324900663472
>>> id(li2[-1])  # 第二层(小容器id号),查看第一层容器中的小容器list的内存地址id号
2324900812864
>>> li2  # 可以看到 li2 与li1 不管是第一层。还是第二层的内存地址id号都全部一样
[a, b, [1, 2]]
>>>

  尝试修改li1,查看li2的变化:

>>> li1[0] = "x"
>>> li1[-1][-1] = 20
>>> li1
[x, b, [1, 20]]
>>> li2  #可以看到li2随着li1而变化,不管是第一层还是第二层都跟着变化,因为内存引用都全部一样。
[x, b, [1, 20]]
>>>
  赋值结论:

        母体                                                副本

    修改不可变类型数据(第一层的str)                                 保持原母体中的值,不发生变化    

    修改可变数据类型中的数据(第二层小容器list中的数据)                    保持原母体中的值,不发生变化  

底层原理

技术图片

Python 浅拷贝示例

  浅拷贝。用到list数据类型自带的方法,.copy()。我们来看一看会怎么样:

>>> li1 = ["a","b",[1,2]] # 注意存储的数据类型。第一层存储2个不可变类型,1个可变类型小容器(list),第二层存储2个不可变类型
>>> id(li1)  # 第一层,查看变量名所指向的列表(第一层容器)的内存地址id号
3120558308288
>>> id(li1[0])  # 第一层,查看第一层容器中的具体元素内存地址id号
3120557838512
>>> id(li1[-1])  # 第二层(小容器id号),查看第一层容器中的小容器list的内存地址id号
3120557987904
>>> li1
[a, b, [1, 2]]
>>> #=============手动分割线=============
>>> li2 = li1.copy()
>>> id(li2)  # 第一层,查看变量名所指向的列表(第一层容器)内存地址id号
3120558308352
>>> id(li2[0])  # 第一层,查看第一层容器中的具体元素内存地址id号
3120557838512
>>> id(li2[-1])  # 第二层(小容器id号),查看第一层容器中的小容器list的内存地址id号
3120557987904
>>> li2  # 可以看到 li2 与li1 第一层的内存地址已经发生了变化。只有第二层的内存引用地址一样
[a, b, [1, 2]] 
>>>

  尝试修改li1,查看li2的变化:

>>> li1[0] = "x"
>>> li1[-1][-1] = 20
>>> li1
[x, b, [1, 20]] 
>>> li2  # li2 仅仅只有第二层小容器list中的值发生了变化。而第一层中的str不可变类型并没有发生变化
[a, b, [1, 20]]
>>>

  浅拷贝结论:

         母体                                               副本

    修改不可变类型数据(第一层的str)                               保持原母体中的值,不发生变化    

    修改可变数据类型中的数据(第二层小容器list中的数据)                  不保持原母体中的值,跟随母体变化                                               

底层原理

技术图片

 

 

Python 深拷贝示例

  使用深拷贝需要导入Python的内置库。copy,具体使用方式还是看代码:

>>> from copy import deepcopy  # deep深度的意思,copy就拷贝。
>>> li1 = ["a","b",[1,2]] # 注意存储的数据类型。第一层存储2个不可变类型,1个可变类型小容器(list),第二层存储2个不可变 类型
>>> id(li1)  # 第一层,查看变量名所指向的列表(第一层容器)的内存地址id号
3120558351168
>>> id(li1[0])  # 第一层,查看第一层容器中的具体元素内存地址id号
3120557838512
>>> id(li1[-1])  # 第二层(小容器id号),查看第一层容器中的小容器list的内存地址id号
3120558353280
>>> li1
[a, b, [1, 2]]
>>>  #=============手动分割线=============
>>> li2 = deepcopy(li1)
>>> id(li2)  # 第一层,查看变量名所指向的列表(第一层容器)内存地址id号
3120558308288
>>> id(li2[0])  # 第一层,查看第一层容器中的具体元素内存地址id号
3120557838512
>>> id(li2[-1])  # 第二层(小容器id号),查看第一层容器中的小容器list的内存地址id号
3120558904448
>>> li2  # 可以看到 li2 与li1 第一层的内存地址已经发生了变化。只有第二层的内存引用地址一样
[a, b, [1, 2]]
>>>

  尝试修改li1,查看li2的变化:

>>> li1[0] = "x"
>>> li1[-1][-1] = 20
>>> li1
[x, b, [1, 20]]
>>> li2  # li2 由于小容器也新生成了一个。所以即使li1小容器中的值发生改变,li2小容器中的值依然是原本的值
[a, b, [1, 2]]
>>>

  深拷贝结论:

       母体                                               副本

    修改不可变类型数据(第一层的str)                              保持原母体中的值,不发生变化    

    修改可变数据类型中的数据(第二层小容器list中的数据)                 保持原母体中的值,不发生变化                                   

底层原理

技术图片

 

其他图示

 

b = a::赋值引用,a 和 b 都指向同一个对象。

技术图片

 

 

b = a.copy():浅拷贝, a 和 b 是一个独立的对象,但他们的子对象还是指向统一对象(是引用)。

 

技术图片

 

b = copy.deepcopy(a): 深度拷贝, a 和 b 完全拷贝了父对象及其子对象,两者是完全独立的。

 

技术图片

总结

从底层剖析Python深浅拷贝(超详细)

 

赋值 =

 

- 不生成任何新的对象,与母体共有所有。

 

- 或者说就是母体本身改了个名而已

 

浅拷贝 .copy()

 

- 生成第一层引用对象(大容器),不生成其他新的引用对象(小容器)

 

- 大容器中的元素不会随着母体改变,小容器中的元素会随着母体改变

 

- 一定程度上与母体做出了差异化,但仍然不够彻底

 

 

 

深拷贝 deepcopy():

 

- 生成母体中所有存在的引用对象(大,小容器)

 

- 无论大容器还是小容器,所有内容都不会随着母体改变而改变

 

- 完全区分与母体,成为独立的个体

 

 

 

应用场景:

 

- 母体只有一个大容器内部无任何小容器(list,dict)等等即可直接使用浅拷贝

 

- 母体大容器中还存在其他小容器(listdictset)等等则使用深拷贝。

 

- 注意:以上说的所有小容器均是可变类型。

 

 

 

方法:

 

- from copy import deepcopy  # 深拷贝

 

以上是关于从底层剖析Python深浅拷贝(超详细)的主要内容,如果未能解决你的问题,请参考以下文章

python——赋值与深浅拷贝

(转)(终极改良版)python基础番外——赋值与深浅拷贝

jenkins详细教程

超详细STL之基于源码剖析vector实现原理及注意事项

超详细STL之基于源码剖析vector实现原理及注意事项

深浅拷贝