Python中的引用賦值,深拷貝,淺拷貝

摘要:Python,引用賦值深拷貝,淺拷貝

總結(jié)一下Python中的變量的引用賦值,深拷貝和淺拷貝,先上結(jié)論

  • 賦值引用會直接將內(nèi)存地址傳遞過去,此時變量間不僅值相等內(nèi)存地址也相等,是同一個對象
  • 賦值存在緩存重用的情況,當重復(fù)定義小整數(shù)字符串變量時,或者在同一個代碼塊中,內(nèi)存中已經(jīng)存在的值會直接被引用,不需要重新創(chuàng)建對象。
  • 組合變量存在賦值引用,深拷貝,淺拷貝三種模式,賦值引用將組合對象的每一層地址全部引用,淺拷貝創(chuàng)建新對象,但是內(nèi)部元素引用舊對象的內(nèi)部元素,深拷貝所有地址全部重新創(chuàng)建,不依賴之前任何的內(nèi)存地址。
  • 和原始對象的關(guān)系或者聯(lián)系緊密程度:賦值引用 > = 淺拷貝 >= 深拷貝
  • 內(nèi)存的復(fù)用程度:賦值引用 > = 淺拷貝 >= 深拷貝
  • 變量的嶄新程度:深拷貝 >= 淺拷貝 >= 賦值引用

引用賦值

在python中,使用等號無論是直接的變量賦值,還是參數(shù)傳遞,都是按照引用進行賦值的,等號賦值是指將對象的內(nèi)存指針賦值。
在Python中對于非組合變量(對象中不包含其他對象,只有一層地址關(guān)系)的賦值直接使用等號賦值即可,即將一個變量賦值給另一個變量,則兩者不僅在數(shù)值上相等,而且還是同一對象,id相同is判斷為True這兩個變量都指向這一個數(shù)據(jù)對象,即這個數(shù)據(jù)對象有兩個引用,只有這兩個引用都沒了的時候,堆內(nèi)存中的數(shù)據(jù)對象才會等待垃圾回收器回收

>>> # 將一個變量賦值給另一個變量
>>> a = 10000
>>> b = a
>>> id(a)
140139864296144
>>> id(b)
140139864296144
>>> a is b
True

如果單獨對兩個變量賦值,就算賦值的數(shù)據(jù)對象相同,也只是數(shù)據(jù)對象的值相等,兩個變量在堆內(nèi)存中是不同的對象。

>>> a = 10000
>>> b = 10000
>>> id(a)
140139864296272
>>> id(b)
140139864296048
>>> a is b
False

可變對象,不可變對象對引用賦值的影響

如果變量的值是不可變對象,比如string,int,float,tuple等,其中一個變量重新賦值意味著在內(nèi)存中要新創(chuàng)建一個數(shù)據(jù)對象,此時變量間不會相互影響,即不會影響另外一個變量。

>>> a = 10000
>>> b = a
>>> id(a)
140139864296272
>>> id(b)
140139864296272
>>> a = 20000  # 改變a的值
>>> id(a)  # a在堆內(nèi)存中新創(chuàng)建了一個對象,獲得新的id地址
140139864295920
>>> b  # b不變
10000
不可變對象賦值引用改變變量值.png

對于可變對象,調(diào)用自身方法使的值改變不會改變內(nèi)存地址。比如列表list,字典dict,集合set,如果是在原數(shù)據(jù)對象自身上做修改,比如修改列表中的某個元素,列表的地址不會變,還是原來的那個內(nèi)存對象,此時調(diào)用另一個變量的值也會改變,因為是同一個內(nèi)存對象的兩個引用,因此對于可變對象的引用賦值,變量之間是相互影響的。

>>> a = {"a": 1, "b": 3}
>>> b = a
>>> b["c"] = 9  # 改變其中一個變量,給字典新增一個值
>>> a  # 原始變量也會收到影響
{'a': 1, 'b': 3, 'c': 9}
>>> id(a)
140139842087104
>>> id(b)
140139842087104  # 內(nèi)存地址未改變,只是新增了一個元素
可變對象賦值引用改變變量值.png

緩存的重用機制

Python會根據(jù)對象的讀取頻繁程度以及內(nèi)存占用情況,按照一定規(guī)則將對象存入緩存。當程序的其他代碼使用這些值的時候,會先去緩存中找并且直接引用緩存中的地址,不需要額外創(chuàng)建,這些值包括:[-5, 256]之間的小整數(shù),字符串對象。

>>> a = -3
>>> b = -3
>>> a is b
True
>>> b = 2234 - 2237
>>> a is b
True

超過256分別賦值就是不同對象了

>>> a = 257
>>> b = 257
>>> a is b
False

字符串是直接讀取緩存中地址

>>> a = "aaaabbbbcccc"
>>> b = "aaaabbbbcccc"
>>> a is b
True

除此之外,其他不可變變量如果處在同一個代碼塊或者同行,也直接獲取代碼塊中緩存的變量,不再另外創(chuàng)建

>>> # 同行
>>> a = 20000; b = 20000
>>> a is b
True
>>> a = 20000
>>> b = 20000
>>> a is b
False

同一個函數(shù)代碼塊

>>> def a():
...     a = -2836755.383274835
...     b = -2836755.383274835
...     print(a is b)
...     print(id(a))
...     print(id(b))
... 
>>> a()
True
140139864296112
140139864296112

深拷貝和淺拷貝

首先深拷貝和淺拷貝是針對組合對象的,組合對象就是這個對象中還包含其他對象,比如list,set,dict等,也就是說這個對象有不止一層內(nèi)存地址,非組合對象都是直接等號賦值。

  • 淺拷貝創(chuàng)建一個新的組合變量,但是組合變量中每一個元素指向拷貝的對象內(nèi)元素地址
  • 深拷貝創(chuàng)建一個新的組合變量,原對象中的每個元素都會在新對象中重新創(chuàng)建一次

對于組合對象list,set,dict(沒有tuple)自帶copy淺拷貝方法,深拷貝需要導入copy模塊,調(diào)用deepcopy方法,copy的copy方法對應(yīng)淺拷貝

# 淺拷貝
>>> a = [1, 2, 3]
>>> b = a.copy()
>>> print(a is b)
False
>>> for i, j in zip(a, b):
...     print(id(i))
...     print(id(j))
...     print(i is j)
... 
93994145530624
93994145530624
True
93994145530656
93994145530656
True
93994145530688
93994145530688
True

淺拷貝后新對象地址不一樣,但是內(nèi)部元素引用一致


>>> a = [1, 2, 3]
>>> import copy
>>> b = copy.deepcopy(a)
>>> print(a is b)
False
>>> for i, j in zip(a, b):
...     print(id(i))
...     print(id(j))
...     print(i is j)
... 
93994145530624
93994145530624
True
93994145530656
93994145530656
True
93994145530688
93994145530688
True

深拷貝新建了一個對象,但是內(nèi)部元素也是引用的原始地址,原因是內(nèi)部元素是小整數(shù),小整數(shù)在內(nèi)存中有緩存直接調(diào)用,換一下內(nèi)部元素為可變對象。

>>> a = [[1, 2], [2, 3], [3, 4]]
>>> b = copy.deepcopy(a)
>>> print(a is b)
False
>>> for i, j in zip(a, b):
...     print(id(i))
...     print(id(j))
...     print(i is j)
... 
140139841222048
140139840826496
False
140139841244208
140139840826816
False
140139841244128
140139840826976
False

原型畢露,深拷貝不僅對象,連對象內(nèi)部的元素都重新創(chuàng)建,在用新數(shù)據(jù)試下淺拷貝

>>> a = [[1, 2], [2, 3], [3, 4]]
>>> b = copy.copy(a)
>>> print(a is b)
False
>>> for i, j in zip(a, b):
...     print(id(i))
...     print(id(j))
...     print(i is j)
... 
140139841222048
140139841222048
True
140139841244208
140139841244208
True
140139841244128
140139841244128
True

厲害,淺拷貝只是創(chuàng)建新對象,內(nèi)部元素還是老的引用。


在對比一下組合對象的賦值引用

>>> a = [[1, 2], [2, 3], [3, 4]]
>>> b = a
>>> print(a is b)
True
>>> for i, j in zip(a, b):
...     print(id(i))
...     print(id(j))
...     print(i is j)
... 
140139841222048
140139841222048
True
140139841244208
140139841244208
True
140139841244128
140139841244128
True

可見賦值引用所有地址全部拷貝去了,既不要創(chuàng)建新對象,內(nèi)部元素也全是來的對象內(nèi)部元素的引用。

總結(jié)

  • 賦值引用會之間將內(nèi)存地址傳遞過去,此時變量間不僅值相等,內(nèi)存地址也相等,是同一個對象。
  • 分別賦值存在緩存重用的情況,當重復(fù)定義小整數(shù)字符串變量時,或者在同一個代碼塊中,內(nèi)存中已經(jīng)存在的值會直接被引用,不需要重新創(chuàng)建對象。
  • 組合變量存在賦值引用深拷貝,淺拷貝三種模式,賦值引用將組合對象的每一層地址全部引用,淺拷貝創(chuàng)建新對象,但是內(nèi)部元素引用舊對象的內(nèi)部元素,深拷貝所有地址全部重新創(chuàng)建,不依賴之前任何的內(nèi)存地址。

最后再用一個例子對比一下組合對象的三種拷貝方式

>>> a = [[1, 2], [2, 3], [3, 4]]
>>> for line in a:
...     line[1] = 9  # 直接拿到組合變量的內(nèi)部元素進行修改
... 
>>> a
[[1, 9], [2, 9], [3, 9]]
>>> a = [[1, 2], [2, 3], [3, 4]]
>>> for line in a.copy():  # 生成一個新對象遍歷,但是內(nèi)部元素還是舊元素的引用,內(nèi)部元素是list,地址沒變,只是list的第二個值改了,因此也會對原對象產(chǎn)生影響
...     line[1] = 9
... 
>>> a
[[1, 9], [2, 9], [3, 9]]
>>> a = [[1, 2], [2, 3], [3, 4]]
>>> for line in copy.deepcopy(a):  # 創(chuàng)建新元素遍歷,內(nèi)部元素也是新創(chuàng)建的和之前沒有任何關(guān)系,所以修改內(nèi)部元素不會對原來變量造成影響
...     line[1] = 9
... 
>>> a
[[1, 2], [2, 3], [3, 4]]

再來一個例子

>>> a = [[1, 2], [2, 3], [3, 4]]
>>> for line in a:
...     b = line
...     b[1] = 9  # 直接拿到值,調(diào)用自身方法修改值
... 
>>> a
[[1, 9], [2, 9], [3, 9]]
>>> a = [[1, 2], [2, 3], [3, 4]]
>>> for line in a:
...     b = line.copy()  # 內(nèi)嵌list更改內(nèi)存地址,內(nèi)嵌list的整數(shù)元素是舊引用,但是由于新賦值了9,所有指針全部重新指向9,舊對象和舊元素不變
...     b[1] = 9
... 
>>> a
[[1, 2], [2, 3], [3, 4]]
>>> for line in a:
...     b = copy.deepcopy(line)  # 深拷貝更不用說有,全部重新創(chuàng)建,和之前不搭嘎
...     b[1] = 9
... 
>>> a
[[1, 2], [2, 3], [3, 4]]

徹底懂了吧

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容