Python中的赋值、浅拷贝和深拷贝

关于这个值传递、引用传递之类的问题 老是容易弄混淆,这次通过代码来好好梳理一遍。

(一)赋值“=”
>>> alist = ["lllii", 23, ["Redis", "Mysql"]]
>>> blist = alist

>>> print(id(alist))
3070901452
>>> print(id(blist))
3070901452

>>> alist[0] = "llliiiblist"
>>> alist[2].append("Pgsql")

>>> print(alist)
['llliiiblist', 23, ['Redis', 'Mysql', 'Pgsql']]
>>> print(blist)
['llliiiblist', 23, ['Redis', 'Mysql', 'Pgsql']]

>>> print(id(alist))
3070901452
>>> print(id(blist))
3070901452

>>> blist[0] = "llliiiblist-again"
>>> print(blist)
['llliiiblist-again', 23, ['Redis', 'Mysql', 'Pgsql']]
>>> print(alist)
['llliiiblist-again', 23, ['Redis', 'Mysql', 'Pgsql']]

从代码可以看出,使用=赋值的alist和blist指向同一个内存id,因此无论怎么改alist或者blist,两个都是一起变化的。也就是说,对象的赋值就是内存地址的传递。

(二)浅拷贝
>>> import copy

>>> alist = ["lllii", 23, ["Redis", "Mysql"]]
>>> blist = copy.copy(alist)

>>> print(id(alist))
3070854988
>>> print(id(blist))
3069998540

>>> print([id(e) for e in alist])
[3069967808, 138111136, 3070901452]
>>> print([id(e) for e in blist])
[3069967808, 138111136, 3070901452]

>>> alist[0] = "ooooohehaha"
>>> alist[2].append("Mongodb")

>>> print(alist)
['ooooohehaha', 23, ['Redis', 'Mysql', 'Mongodb']]
>>> print(blist)
['lllii', 23, ['Redis', 'Mysql', 'Mongodb']]

>>> print([id(e) for e in alist])
[3069973232, 138111136, 3070901452]
>>> print([id(e) for e in blist])
[3069967808, 138111136, 3070901452]

>>> blist[0] = "changefromblist"
>>> blist[2].append('Pgsql')

>>> print(alist)
['ooooohehaha', 23, ['Redis', 'Mysql', 'Mongodb', 'Pgsql']]
>>> print(blist)
['changefromblist', 23, ['Redis', 'Mysql', 'Mongodb', 'Pgsql']]

>>> print([id(e) for e in alist])
[3069973232, 138111136, 3070901452]
>>> print([id(e) for e in blist])
[3069973952, 138111136, 3070901452]

代码中,先使用copy模块,把alist对象浅拷贝给blist,两个对象的内存id是不一样的,说明blist是一个新的对象。
但是,对于对象中的元素,浅拷贝就只会使用原始元素的内存地址,也就是说”blist[i] is alist[i]”。
alist中第一个元素string是不可变类型,修改后该元素的内存id发生了变化;
alist中第三个元素list是可变类型,修改后该元素的内存id不变。blist同理。

使用以下操作的时候,会产生浅拷贝的效果:
1)使用切片[:]操作
2)使用工厂函数(如list/dir/set)
3)使用copy模块中的copy()函数

小结:浅拷贝产生的新对象,虽然具有完全不同的id,但是其值若包含可变对象,这些对象和原始对象中的值包含同样的引用。使用浅拷贝的典型使用场景是:对象自身发生改变的同时需要保持对象中的值完全相同,比如 list 排序。

(三)深拷贝
>>> import copy

>>> alist = ["lllii", 23, ["Redis", "Mysql"]]
>>> blist = copy.deepcopy(alist)

>>> print(id(alist))
3061911756
>>> print(id(blist))
3069967756

>>> print([id(e) for e in alist])
[3069965696, 138111136, 3061907116]
>>> print([id(e) for e in blist])
[3069965696, 138111136, 3069967692]

>>> alist[1] = 25
>>> alist[2].append('Oracle')

>>> print(alist)
['lllii', 25, ['Redis', 'Mysql', 'Oracle']]
>>> print(blist)
['lllii', 23, ['Redis', 'Mysql']]

>>> print(id(alist))
3061911756
>>> print(id(blist))
3069967756

>>> print([id(e) for e in alist])
[3069965696, 138111168, 3061907116]
>>> print([id(e) for e in blist])
[3069965696, 138111136, 3069967692]

>>> blist[0] = 'changeeeee'
>>> blist[2].append('Oracleeeee')

>>> print(blist)
['changeeeee', 23, ['Redis', 'Mysql', 'Oracleeeee']]
>>> print(alist)
['lllii', 25, ['Redis', 'Mysql', 'Oracle']]

>>> print([id(e) for e in alist])
[3069965696, 138111168, 3061907116]
>>> print([id(e) for e in blist])
[3069973952, 138111136, 3069967692]

深拷贝后,blist的内存id与alist不同,但是三个元素,只有alist[2]和blist[2]的内存id不同,但是修改了alist[1]和blist[0]后,他们的内存id也改变了。原因是,对于[0],[1]两种不可变类型,是没有copy的说法的,深拷贝浅拷贝都不会改变他们的内存id。而对于[2]可变类型,深拷贝后,会创建新的对象。

小结:深拷贝不仅仅拷贝了原始对象自身,也对其包含的值进行拷贝,它会递归的查找对象中包含的其他对象的引用,来完成更深层次拷贝。因此,深拷贝产生的副本可以随意修改而不需要担心会引起原始值的改变。

拓展

(1)可变类型和不可变类型

  • 可变类型:list,dict等。
    以list为例,对alist进行修改,其内存地址不变。
>>> alist = [1, 4, 'hahha']
>>> id(alist)
3069967756
>>> alist.append('lueluelue')
>>> id(alist)
3069967756
>>> b = alist
>>> id(b)
3069967756
  • 不可变类型:数字、string、tuple等。
    以int类型为例, num += 1 并不是在原有的int对象上+1,而是重新创建一个值为2的int对象,num引用自这个新的对象。当b赋值为2时,b也引用了值为2的int对象。(注意:两个相同值的浮点型变量 内存地址是不一样的。)
>>> num = 1
>>> id(num)
138110784
>>> 
>>> num += 1
>>> id(num)
138110800
>>> 
>>> num
2
>>> b = 2
>>> id(b)
138110800


>>> x = 1.5
>>> id(x)
3066236256
>>> y = 1.5
>>> id(y)
3066235424

(2)什么是工厂函数?
工厂函数:能够产生类实例的内建函数。这些内建函数都是类对象, 当调用它们时,实际上是创建了一个类实例。
内建函数:安装好Python后就可以直接使用的函数,不需要import任何模块)
例如工厂函数list,将tuple转为list,创建了一个新的对象,很明显是浅拷贝。

>>> a = (1, 5, [2, 3])
>>> alist = list(a)
>>> print([id(e) for e in a])
[138110784, 138110848, 3071092556]
>>> print([id(e) for e in alist])
[138110784, 138110848, 3071092556]

>>> alist[2].append("haha")
>>> print([id(e) for e in alist])
[138110784, 138110848, 3071092556]

>>> id(a)
3071147420
>>> id(alist)
3071149484

参考链接:
http://wecatch.me/blog/2016/06/18/python-copy-deepcopy/
https://www.cnblogs.com/wilber2013/p/4645353.html
https://www.cnblogs.com/blackmatrix/p/5614086.html
http://www.pythontab.com/html/2018/pythonjichu_0321/1263.html
http://www.jb51.net/article/136819.htm

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值