python 的深浅拷贝和赋值分析

一、python中的赋值操作

先上个栗子:

# 赋值
a = "bob"
# 查询变量a的内存地址
id(a)
139941618330008

第一行代码非常简单,给变量a赋值字符串"bob"。在python中赋值操作的本质是在内存中开辟出一块地方存放某个对象,然后变量再指向存放这个对象的内存地址,有点类似C中的指针含义。那么上面这句代码就是在内存开辟一块地址存放字符串"bob",然后再将变量a指向这块内存的地址,查询某个变量指向的内存地址的命令用id(),下面输出的一串数字则是a的地址引用

二、深浅拷贝

1、字符串、数字的深浅拷贝

看代码

>>> a = "bob"
>>> import copy    #导入copy模块
>>> b = copy.copy(a)    # 浅拷贝
>>> id(a),id(b)
(140615646648616, 140615646648616)    # 浅拷贝后的两个变量指向的是同一块内存地址
>>> 
>>> c = copy.deepcopy(a)    # 深拷贝
>>> id(a), id(c)
(140615646648616, 140615646648616)    # 深拷贝后的两个变量指向的还是同一块内存地址
>>> 

代码一目了然,无论深拷贝还是浅拷贝,最终三个变量所指向的都是同一块内存地址,也就是说当某一个变量改变时,改变的只是他的地址引用,而之前那块内存中的数据没变,另外拷贝的变量的值自然也不会变,再看代码

>>> a = a + " john"    # 改变a的值
>>> a, b, c
('bob john', 'bob', 'bob')
>>> id(a), id(b), id(c)
(140615646680368, 140615646648616, 140615646648616)    # a 的地址引用变了,其他两人个没变
>>> 

结论:深浅拷贝对于字符串和数字来说并没有任何区别,拷贝的都是内存地址

 

2、列表、元组、字典的深浅拷贝

同样看简单代码

>>> l1 = ['a', 'b', 'c']
>>> l2 = copy.copy(l1)
>>> l3 = copy.deepcopy(l1)
>>> l1, l2, l3
(['a', 'b', 'c'], ['a', 'b', 'c'], ['a', 'b', 'c'])    #对列表l1所做的深浅拷贝的列表l2和l3,值一样
>>> id(l1), id(l2), id(l3)
(140615646660360, 140615646660040, 140615646662472)    # 三个列表的内存地址完全不一样
>>> 

通过上面代码不难看出,无论深浅拷贝,对列表(元组/字典一样)来说,都是新开辟一块内存,两块内存中存放的数据一样,这样改变一个列表时也只是改变了一块内存地址中的数据,并不影响其他的拷贝

>>> l1.append('d')
>>> l1, l2, l3
(['a', 'b', 'c', 'd'], ['a', 'b', 'c'], ['a', 'b', 'c'])
>>> id(l1), id(l2), id(l3)
(140615646660360, 140615646660040, 140615646662472)    # 三个列表内存地址没变,只是列表1指向的内存中的数据发生了变化
>>> 

由此得出结论,列表、元组、字典等的深浅拷贝都是在内存中重新开辟一块地址,一个改变,不影响其他拷贝。

如果只是这种结论,那么深浅拷贝就不必做为一个知识点被所有python同鞋们反复学习了。上面这种情况只适用于一层结构的数据类型,也可以说一维数据类型,对于二维或以上的列表来说就有区别了。

>>> ll1 = ['a', 'b', 'c', [ 1, 2, 3]]
>>> ll2 = copy.copy(ll1)
>>> ll3 = copy.deepcopy(ll1)
>>> ll1, ll2, ll3
(['a', 'b', 'c', [1, 2, 3]], ['a', 'b', 'c', [1, 2, 3]], ['a', 'b', 'c', [1, 2, 3]])
>>> id(ll1), id(ll2), id(ll3)
(140615639565192, 140615639565320, 140615646761224)

到这儿代码没什么不同,除了列表中嵌套了列表,是个二维数据,而ll2是ll1的浅拷贝,ll3是ll1的深拷贝。

大家应该还记得我们刚开始说的赋值,是将内存地址的引用赋给变量,而不是实际值。既然是引用,那么对一个列表做拷贝的时候,它中间的每一个元素究竟是对内存地址加了个引用指向呢,还是又另开辟了一块内存来存放每个值呢?没错,这个就是深拷贝和浅拷贝的不同。

首先,字符串和数字我们说过无论深浅拷贝都是地址引用的增加,也就是说'a'这个字符串无论在哪里,在几个列表中,它的内存地址永远不会变。我们就要看列表中所嵌套的可变数据类型,在这里就是二维列表[1, 2, 3],我们直接上代码

>>> id(ll1[0]), id(ll2[0]), id(ll3[0])    # 先做个实验,查看三个列表中第一个元素,也就是字符串'a'的地址,发现果然一样,只是地址引用的增加
(140615677439088, 140615677439088, 140615677439088)
>>>
>>> id(ll1[3]), id(ll2[3]), id(ll3[3])    # 再看三个列表中的嵌套列表,发现浅拷贝的内存地址相同,而深拷贝的则属于新开辟内存,地址不同
(140615639567240, 140615639567240, 140615646716168)

代码已经很清楚了,对列表中的列表(无论多少层),浅拷贝只是将地址的引用指向增加了;而深拷贝则是另外开辟一块新内存放数据。接下来就好理解了,当我改变了ll1的内嵌列表的数据时,改变的是它指向的内存地址的数据,那么通过浅拷贝和他有相同指向的ll2的内嵌列表中的数据也会跟着发生变化,而通过深拷贝的ll3的内嵌列表因为是新开辟的内存,则不会受影响,看代码

>>> ll1[3].append(4)
>>> ll1, ll2, ll3
(['a', 'b', 'c', [1, 2, 3, 4]], ['a', 'b', 'c', [1, 2, 3, 4]], ['a', 'b', 'c', [1, 2, 3]])    # 深拷贝数据不受影响
>>> id(ll1[0]), id(ll2[0]), id(ll3[0])    # 内嵌列表地址没变
(140615677439088, 140615677439088, 140615677439088)
>>> 

结论:对只有一层的列表(元组、字典),深浅拷贝没什么区别;对大于一层的列表(元组、字典),当原数据中内层数据结构发生变化时,浅拷贝也会受影响跟着变化,深拷贝完全不受影响。

最后引用武sir的两张图,很具体

浅拷贝:

深拷贝:

猜你喜欢

转载自www.cnblogs.com/sqtu/p/10281166.html