Python 中的引用

像 Python、JavaScript 這類的動(dòng)態(tài)語言,基本上都是使用引用賦值的,不管是基本數(shù)據(jù)類型,還是復(fù)雜的數(shù)據(jù)類型,都是按照引用傳值。

引用賦值

在引用賦值中,變量名和變量的真實(shí)值是分開保存的,變量名中保存的是真實(shí)值的一個(gè)指針,對(duì)變量賦值時(shí),也是將這個(gè)指針賦值給新變量。我們?cè)谑褂米兞康臅r(shí)候,不必關(guān)心它們的指針指向哪里,語言內(nèi)部在實(shí)現(xiàn)的時(shí)候會(huì)幫助我們找到真實(shí)值,感覺上變量名和變量的真實(shí)值是保存在一起的,實(shí)際上他們是保存在不同的內(nèi)存空間。
在 Python 語言中,無論什么數(shù)據(jù)類型,都是按照引用進(jìn)行賦值的。
使用 id 方法可以查看變量的真實(shí)值在內(nèi)存空間中的地址(復(fù)雜數(shù)據(jù)類型為首地址):

# 定義基本數(shù)據(jù)類型
a = 100
b = a
print(id(a),id(b))

# 定義復(fù)雜數(shù)據(jù)類型
arr1 = [1,2,3]
arr2 = arr1

print(id(arr1),id(arr2))

運(yùn)行結(jié)果:

506224448 506224448
27666752 27666752

a 和 b,arr1 和 arr2 中都指向了同一份內(nèi)存地址。示意圖如下:

引用賦值圖解.png

注:上圖中的列表 [1,2,3] 應(yīng)該是保存在一片內(nèi)存空間中的,示意圖給簡化了。

賦值操作

初始操作下,a 和 b、arr1 和 arr2 都指向了同一份內(nèi)存空間,此時(shí)如果再對(duì)這些變量進(jìn)行賦值操作,其就會(huì)指向新的內(nèi)存空間。
如下代碼:

# 定義基本數(shù)據(jù)類型
a = 100
b = a
b = 1001
print(id(a),id(b))

# 定義復(fù)雜數(shù)據(jù)類型
arr1 = [1,2,3]
arr2 = arr1
arr2 = [1,2,3,1]

print(id(arr1),id(arr2))

運(yùn)行結(jié)果為:

495673152 25390400
29763904 29736272

可見,在使用 = 重新賦值后,原來的“引用”斷開了,變量重新引用了新的內(nèi)存地址,示意圖如下:

重新引用.png

可變類型與不可變類型

Python 中有下面幾種不可變類型:

  • 數(shù)值型
  • 字符串型
  • 元組

除了不可變類型,其余的都是可變類型,如:

  • 列表
  • 字典

不可變類型,就是無法修改的類型,我們無法在內(nèi)存中直接修改這個(gè)變量(如 100,"student"),如果我們嘗試對(duì)不可變類型進(jìn)行修改,就會(huì)斷開原始的引用,重新分配內(nèi)存地址。
可變類型,就是可以進(jìn)行修改的類型,修改可變類型的值不會(huì)斷開原始引用。
看下如下代碼:

# 定義不可變類型
a = 100
print(id(a))
# 嘗試修改不可變類型的值
a += 1;
print(id(a))

# 定義可變類型
arr = [1,2,3]
print(id(arr))
# 嘗試修改可變類型的值
arr.append(4)
print(id(arr))

代碼執(zhí)行結(jié)果如下:

503603008
503603024
18622784
18622784

我們嘗試修改不可變類型的值,原先的引用斷開,重新分配新的引用。 當(dāng)我們嘗試修改可變類型的值時(shí),原始的引用并不會(huì)斷開。
解釋器在運(yùn)行代碼時(shí),會(huì)根據(jù)變量是否是可變類型來決定在原始值上進(jìn)行修改還是重新引用,因此下面的代碼理解起來就很簡單了:

# 定義不可變類型
# 定義不可變類型
a = 100
b = a
a += 1
print(id(a),id(b))
print(a,b)

# 定義可變類型
arr1 = [1,2,3]
arr2 = arr1
arr1.remove(2)
print(id(arr1),id(arr2))
print(arr1,arr2)```
運(yùn)行結(jié)果如下:

492330832 492330816
101 100
25962816 25962816
[1, 3] [1, 3]

修改可變類型時(shí),是直接在原始值上進(jìn)行修改,因此這些和原始值有引用關(guān)系的變量的“值”都被修改了。嘗試修改不可變類型時(shí),由于不可變類型的值是無法進(jìn)行修改的,因此會(huì)斷開原始的引用,重新分配新的引用,由于原始的值沒有被修改,因此和原始值有引用關(guān)系的變量的“值”保持不變。
## 解釋器對(duì)不可變類型的優(yōu)化
前面說到,在使用 = 進(jìn)行賦值時(shí),會(huì)斷開原先的引用,重新開辟一塊內(nèi)存,讓變量指向新的內(nèi)存的地址。在實(shí)際情況下,解釋器對(duì)我們常用的一些值(數(shù)字、較短的字符串)進(jìn)行了優(yōu)化,只要將某個(gè)值賦值給變量,這些變量的的引用地址都是一樣的,并不會(huì)重新開辟內(nèi)存。
對(duì)于可變類型的值,仍會(huì)重新開啟內(nèi)存。
看下下面的代碼:

定義不可變類型

a = 100
b = 100
c = 100
print(id(a),id(b),id(c))

定義可變類型

arr1 = [1,2]
arr2 = [1,2]
arr3 = [1,2]
print(id(arr1),id(arr2),id(arr3))

運(yùn)行結(jié)果如下:

490626880 490626880 490626880
22489408 22461776 22499736

## 參數(shù)引用傳遞
除了賦值,函數(shù)的參數(shù)也是引用傳遞:

定義不可變類型

a = 100

定義可變類型

arr = [1,2,3]

def modify(v):
v += v

修改 a 的值

modify(100)
print(a)

修改 arr 的值

modify(arr)
print(arr)

運(yùn)行結(jié)果如下:

100
[1, 2, 3, 1, 2, 3]

**在函數(shù)調(diào)用時(shí),傳遞給函數(shù)的參數(shù)中保存的仍然是變量的引用(真實(shí)值的地址)**,函數(shù)參數(shù)是一個(gè)局部變量,初始時(shí),參數(shù)和函數(shù)外部的值指向同一份內(nèi)存地址。如果需要在函數(shù)內(nèi)部對(duì)參數(shù)進(jìn)行修改,同樣遵循上面的規(guī)則:
- 如果參數(shù)是不可變類型,則斷開原始引用,重新進(jìn)行新的引用,函數(shù)外部的值不受影響
- 如果參數(shù)是可變類型,直接修改原始值,函數(shù)外部的值同步變化

## 總結(jié)
本文學(xué)習(xí)了 Python 中的引用,總結(jié)以下幾點(diǎn):
- 所有的變量賦值、參數(shù)傳遞等都是引用傳遞
- 修改變量值時(shí),解釋器根據(jù)變量是否可變決定是修改原始值還是重新進(jìn)行引用
- 如果變量是可變類型,則所有和原始值有引用關(guān)系的變量都會(huì)受到影響
- 如果變量是不可變類型,則所有和原始值有引用關(guān)系的變量不受影響
- 解釋器對(duì)常用的不可變類型進(jìn)行了優(yōu)化,把常用的數(shù)值、短字符串賦值給某些變量時(shí),這些變量都指向相同的內(nèi)存地址

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

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

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