像 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)存地址。示意圖如下:

注:上圖中的列表 [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)存地址,示意圖如下:

可變類型與不可變類型
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)存地址
完。