Python中變量賦值傳遞時的引用和拷貝


一、變量、對象和引用的關系

1.變量

  所有的變量必須在其使用前明確地賦值,使用未賦值的變量會產生錯誤,變量在賦值的時候才創建,它可以引用任何類型的對象。

>>> print(a)    #變量a未明確賦值,產生錯誤
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined

2.引用

  在Python中從變量到對象的連接稱作引用。也就是說,引用是一種關系,以內存中的指針的形式實現。一旦變量被使用(也就是說被引用),Python自動跟隨這個變量到對象的連接。以具體的術語來講:

  • 變量是一個系統級的元素,擁有指向對象的連接的空間。
  • 對象是分配的一塊內存,有足夠的空間去表示它們所代表的值。
  • 引用是自動形成的從變量到對象的指針。

3.對象 

  Python中一切皆為對象,不管是集合變量還是數值型or字符串型的變量都是一個引用,都指向對應內存空間中的對象。

一個對象都有兩個標准的頭部信息:一個類型標識符去標識這個對象的類型,以及一個引用的計數器,用來決定是不是可以回收這個對象。

 

二、賦值生成引用,而不是拷貝

1.賦值

  賦值操作總是儲存對象的引用,而不是這些對象的拷貝。

a = 3
b = a
print("a的ID:",id(a))
print("b的ID:",id(b))
#相同的內存空間
輸出:
a的ID: 1631576880
b的ID: 1631576880

變量a賦值給變量b,而b變量引用了與a變量相同的對象,即指向了相同的內存空間,這在Python中叫做共享引用---多個變量名引用了同一個對象。

  (1)當a,b是可變對象時,b和a指向同一個內存空間,改變a中的值(a的內存空間地址不變),則b跟着改變(b的內存空間地址不變);同樣的,改變b中的值,則a跟着改變。

a = [1,2,3]
b = a
a[1] = "spam"
print("a=",a)
print("b=",b)
print("a的ID:",id(a))
print("b的ID:",id(b))

#輸出
a= [1, 'spam', 3]
b= [1, 'spam', 3]
a的ID: 38728368
b的ID: 38728368

 

  (2)當a,b是不可變對象時,b和a指向同一個內存空間,改變a中的值(a的內存空間地址改變),這時b不跟着改變(因為b的內存空間地址不變);同樣的,改變b中的值,這時a不跟着改變。

a = 3
b = a
a = 1
print("a=",a)
print("b=",b)
print("a的ID:",id(a))
print("b的ID:",id(b))

#輸出
a= 1
b= 3
a的ID: 1632559888
b的ID: 1632559920

 

 

2.拷貝

  通過拷貝得到的新變量與原來的變量的內存空間不同。拷貝分為直接拷貝,淺拷貝,深拷貝。

(1)直接拷貝

當我們不知道是引用還是拷貝的情況下,可以顯式的拷貝。

  a.字典copy方法(X.copy())能夠復制字典。

D = {"a":1,"b":2}
A = D.copy()
print(A)
輸出:{'a': 1, 'b': 2}

  b.沒有限制條件的分片表達式(L[:])能夠復制序列。

L = [1,2,3]
B = L[:]
print(B)
輸出:[1, 2, 3]

  c.有些內置函數(例如:list)能夠生成拷貝(list(L))

以上拷貝方法,無條件值的分片以及字典copy方法只能做淺層復制,不能夠復制嵌套的數據結構,如果需要一個深層嵌套的數據結構的完整的、完全獨立的拷貝,那么就要使用標准的copy模塊,即淺拷貝和深拷貝。

(2)淺拷貝

  對於簡單的對象(不可變),深淺拷貝都是一樣的,上面的字典對象的copy方法就是淺拷貝。如果拷貝的對象中即有可變對象,又有不可變對象,則深淺拷貝兩者就有區別。

(3)深拷貝

  深拷貝會完全復制原變量相關的所有數據,在內存中生成一套完全一樣的內容,在這個過程中我們對這兩個變量中的一個進行任意修改都不會影響其他變量。

3.淺拷貝與深拷貝的區別

import copy

a = [1, [1, 2, 3], [4, 5, 6]]
b = a               #賦值
c = copy.copy(a)    #淺拷貝
d = copy.deepcopy(a) #深拷貝

a.append(15)
a[1][2] = 10
a[0] = 0

print(a)
print(b)
print(c)
print(d)

print(id(a))
print(id(b))
print(id(c))
print(id(d))

 

1 #輸出
2 [0, [1, 2, 10], [4, 5, 6], 15]   #a
3 [0, [1, 2, 10], [4, 5, 6], 15]  #b
4 [1, [1, 2, 10], [4, 5, 6]]    #c
5 [1, [1, 2, 3], [4, 5, 6]]     #d
6 38925536 #a的內存空間 7 38925536   #b的內存空間 8 38925496 #c的內存空間 9 38925456   #d的內存空間

 

print('a[1]',id(a[1]))
print('b[1]',id(b[1]))
print('c[1]',id(c[1]))
print('d[1]',id(d[1]))

print('a[0]',id(a[0]))
print('b[0]',id(b[0]))
print('c[0]',id(c[0]))
print('d[0]',id(d[0]))

 

#輸出
a[1] 35124528
b[1] 35124528
c[1] 35124528
d[1] 35124328

a[0] 1631576832 b[0] 1631576832 c[0] 1631576848 d[0] 1631576848

   b和a指向同一個內存空間,改變a中的值,則b跟着改變。c是a淺拷貝,c指向和a不同的內存空間,但是如果a中有一個元素為可變對象,則c中的此元素和a中的元素指向同一個內存空間,則改變a中此可變對象里面的值時,c中的值也改變改變a中不可變對象的元素的值時,c中的值不發生變化。d是a的深拷貝,d和a指向不同的內存空間,d內部的元素和a內部元素也指向不同的空間,改變a里面的值時,d不會發生變化

 總結:

淺拷貝只是增加了一個指針指向一個存在的地址,而深拷貝是增加一個指針並且開辟了新的內存,這個增加的指針指向這個新的內存,

采用淺拷貝的情況,釋放內存,會釋放同一內存,深拷貝就不會出現釋放同一內存的錯誤。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM