Python當中對于拷貝乙嘀,分為兩種類型。一種是數(shù)字和字符串婴噩,另一種就是列表几莽、元組章蚣、字典等其他類型了。
一峭沦、數(shù)字和字符串的拷貝
1吼鱼、賦值
舉個栗子:
a1 = 123123
a2 = 123123
# a2 = a1 # 賦值
print(id(a1)) # 通過id()函數(shù)來打印變量在內(nèi)存當中的地址
print(id(a2))
輸出結(jié)果是:
1959780298352
1959780298352
??在以上代碼塊當中庆尘,a2與a1所賦的值是一樣的,都是數(shù)字123123笑跛。因為python有一個重用機制飞蹂,對于同一個數(shù)字,python并不會開辟一塊新的內(nèi)存空間惊窖,而是維護同一塊內(nèi)存地址界酒,只是將該數(shù)字對應的內(nèi)存地址的引用賦值給變量a1和a2。所以根據(jù)輸出結(jié)果凭疮,a1和a2其實對應的是同一塊內(nèi)存地址执解,只是兩個不同的引用罷了。同樣的桶唐,對于a2 = a1尤泽,其實效果等同于“a1 = 123123; a2 = 123123”熊咽,它也就是將a1指向123123的引用賦值給a2横殴。字符串跟數(shù)字的原理雷同衫仑,如果把123123改成“abcabc”也是一樣的。*
結(jié)論:對于通過用 = 號賦值瞄崇,數(shù)字和字符串 在內(nèi)存當中用的都是同一塊地址苏研。
2、淺拷貝
import copy # 使用淺拷貝需要導入copy模塊
a1 = 123123
a3 = copy.copy(a1) # 使用copy模塊里的copy()函數(shù)就是淺拷貝了
print(id(a1))
print(id(a3))
輸出結(jié)果是:
35233168
35233168
??通過使用copy模塊里的copy()函數(shù)來進行淺拷貝纹蝴,把a1拷貝一份賦值給a3,查看輸出結(jié)果發(fā)現(xiàn)兼犯,a1和a3的內(nèi)存地址還是一樣切黔。
結(jié)論:對于淺拷貝,數(shù)字和字符串在內(nèi)存當中用的也是同一塊地址诗芜。
3孩哑、深拷貝
舉個栗子:
import copy
a1 = 123123
a4 = copy.deepcopy(a1) # 深拷貝是用copy模塊里的deepcopy()函數(shù)
print(id(a1))
print(id(a4))
輸出結(jié)果為:
31432080
31432080
查看結(jié)果發(fā)現(xiàn)横蜒,對于深拷貝,數(shù)字和字符串在內(nèi)存當中用的也是同一塊地址。
所以綜上所述孟岛,對于數(shù)字和字符串的賦值、淺拷貝次询、深拷貝在內(nèi)存當中用的都是同一塊地址。原理如下圖:
二、字典蔽介、列表、元組等其他類型的拷貝
1薇组、賦值
舉個栗子:
n1 = {"k1": "wu", "k2": 123, "k3": ["alex", 678]}
n2 = n1 # 賦值
print(id(n1))
print(id(n2))
輸出結(jié)果:
2235551677536
2235551677536
??我們的栗子當中用了一個字典n1专钉,字典里面嵌套了一個列表跃须,當我們把n1賦值給n2時,內(nèi)存地址并沒有發(fā)生變化第练,因為其實它也是只是把n1的引用拿過來賦值給n2而已。(我們用了一個字典來舉例婴梧,其他類型也是一樣的)
原理如下圖:
結(jié)論:對于賦值,字典番电、列表、元組等其他類型用的內(nèi)存地址不會變化洼冻。
2撞牢、淺拷貝
舉個栗子:
import copy
n1 = {"k1": "wu", "k2": 123, "k3": ["alex", 678]}
n3 = copy.copy(n1) # 淺拷貝
print("第一層字典的內(nèi)存地址:")
print(id(n1))
print(id(n3))
print("第二層嵌套的列表的內(nèi)存地址:")
print(id(n1["k3"]))
print(id(n3["k3"]))
輸出結(jié)果:
第一層字典的內(nèi)存地址:
6516024
6516096
第二層嵌套的列表的內(nèi)存地址:
36995720
36995720
??通過以上結(jié)果可以看出,進行淺拷貝時,我們的字典第一層n1和n3指向的內(nèi)存地址已經(jīng)改變了蟹但,但是對于第二層里的列表并沒有拷貝麦向,它的內(nèi)存地址還是一樣的诵竭。原理如下圖:
結(jié)論:所以對于淺拷貝,字典兼搏、列表卵慰、元組等類型,它們只拷貝第一層地址
3佛呻、深拷貝
舉個栗子:
import copy
n1 = {"k1": "wu", "k2": 123, "k3": ["alex", 678]}
n4 = copy.deepcopy(n1) # 深拷貝
print("第一層字典的內(nèi)存地址:")
print(id(n1))
print(id(n4))
print("第二層嵌套的列表的內(nèi)存地址:")
print(id(n1["k3"]))
print(id(n4["k3"]))
輸出結(jié)果:
第一層字典的內(nèi)存地址:
31157560
35463600
第二層嵌套的列表的內(nèi)存地址:
35947144
35947336
??通過以上結(jié)果發(fā)現(xiàn)裳朋,進行深拷貝時吓著,字典里面的第一層和里面嵌套的地址都已經(jīng)變了鲤嫡。對于深拷貝,它會拷貝多層夜矗,將第二層的列表也拷貝一份泛范,如果還有第三層嵌套让虐,那么第三層的也會拷貝紊撕,但是對于里面的最小元素,比如數(shù)字和字符串赡突,這里就是“wu”对扶,123,“alex”惭缰,678之類的浪南,按照python的機制,它們會共同指向同一個位置漱受,它的內(nèi)存地址是不會變的络凿。原理如下圖:
結(jié)論:對于深拷貝,字典昂羡、列表絮记、元組等類型,它里面嵌套多少層虐先,就會拷貝多少層出來怨愤,但是最底層的數(shù)字和字符串地址不變。
舉個實際應用場景的栗子蛹批。
我們在維護服務器信息的時候撰洗,經(jīng)常會要更新服務器信息篮愉,這時我們重新一個一個添加是比較麻煩的,我們可以把原數(shù)據(jù)類型拷貝一份差导,在它的基礎上做修改试躏。
栗子一、使用淺拷貝
import copy
dic = {
"cpu": [80, ],
"mem": [80, ],
"disk": [80, ]
}
# 定義了一個字典柿汛,存儲服務器信息冗酿。
print('before', dic)
new_dic = copy.copy(dic)
new_dic['cpu'][0] = 50 # 更新cpu為50
print(dic)
print(new_dic)
輸出結(jié)果為:
before {'cpu': [80], 'mem': [80], 'disk': [80]}
{'cpu': [50], 'mem': [80], 'disk': [80]}
{'cpu': [50], 'mem': [80], 'disk': [80]}
這時我們會發(fā)現(xiàn),使用淺拷貝時络断,我們修改新的字典的值之后裁替,原來的字典里面的cpu值也被修改了,這并不是我們希望看到的貌笨。
栗子二弱判、使用深拷貝
import copy
dic = {
"cpu": [80, ],
"mem": [80, ],
"disk": [80, ]
}
print('before', dic)
new_dic = copy.deepcopy(dic)
new_dic['cpu'][0] = 50
print(dic)
print(new_dic)
輸出結(jié)果:
before {'cpu': [80], 'mem': [80], 'disk': [80]}
{'cpu': [80], 'mem': [80], 'disk': [80]}
{'cpu': [50], 'mem': [80], 'disk': [80]}
??使用深拷貝的時候,發(fā)現(xiàn)只有新的字典的cpu值被修改了锥惋,原來的字典里面的cpu值沒有變昌腰。大功告成!
總結(jié)
- 賦值(=):數(shù)據(jù)完全共享(賦值是在內(nèi)存中指向同一個對象膀跌,如果是可變(mutable)類型遭商,比如列表,修改其中一個捅伤,另一個必定改變劫流;如果是不可變類型(immutable),比如字符串,修改了其中一個丛忆,另一個并不會變)
- 淺拷貝:數(shù)據(jù)半共享(復制其數(shù)據(jù)獨立內(nèi)存存放祠汇,但是只拷貝成功第一層)
- 深拷貝:數(shù)據(jù)完全不共享(復制其數(shù)據(jù)完完全全放獨立的一個內(nèi)存,完全拷貝熄诡,數(shù)據(jù)不共享)可很;深拷貝就是完完全全復制了一份,且數(shù)據(jù)不會互相影響凰浮,因為內(nèi)存不共享我抠。