Python 變量?對(duì)象?引用?賦值?一個(gè)例子解釋清楚
哈嘍大家好,我是咸魚。
前天有個(gè)小伙伴找到我,給了我一段 python 代碼:
a = [1, 2]
a[1] = a
print(a[1])
然后問(wèn)我為什么結(jié)果是 [1, [...]],我一看這個(gè)問(wèn)題有意思,我說(shuō)三言兩語(yǔ)解釋不清楚,寫篇文章到時(shí)候你看下吧,于是有了今天這篇文章。
在正式開始之前,讓我們先弄清楚一些概念。
對(duì)象?變量?引用?賦值?
"Python 中一切皆對(duì)象",相信這句話大家在學(xué)習(xí) Python 的時(shí)候都已經(jīng)聽的耳朵起繭子了吧。
在 Python 中,所有的數(shù)據(jù)都是對(duì)象,包括基本數(shù)據(jù)類型(例如整數(shù)、浮點(diǎn)數(shù)、字符串等)以及用戶自定義的類型(類的實(shí)例等)。
而對(duì)象其實(shí)是內(nèi)存中分配的一塊空間,用來(lái)存儲(chǔ)其值。每個(gè)對(duì)象都有一個(gè)唯一的標(biāo)識(shí)符(id),可以通過(guò) id() 函數(shù)獲取。
不但如此,每一個(gè)對(duì)象都具有兩個(gè)標(biāo)準(zhǔn)的頭部信息:
- 類型標(biāo)志符(Type Identifier):每個(gè)對(duì)象都有一個(gè)類型信息,可以通過(guò) type() 函數(shù)獲取。
- 引用計(jì)數(shù)器(Reference Counter): 引用計(jì)數(shù)器表示有多少個(gè)引用指向該對(duì)象,當(dāng)引用計(jì)數(shù)降為零時(shí),對(duì)象會(huì)被垃圾回收。( Python 也使用其他垃圾回收機(jī)制,例如循環(huán)垃圾回收器來(lái)處理引用環(huán)的情況。)
在 Python 中,變量實(shí)際上是對(duì)象的【引用】,而不是對(duì)象本身的【存儲(chǔ)】。當(dāng)我們執(zhí)行賦值語(yǔ)句時(shí),會(huì)自動(dòng)建立變量和對(duì)象之間的關(guān)系,即引用。
變量就像是一個(gè)指針,【指向】?jī)?nèi)存中存儲(chǔ)對(duì)象的位置。
我們來(lái)看一個(gè)例子:
a = 1
b = a
a = a + 1
首先將 1 賦值于 a,即 a 指向了 1 這個(gè)對(duì)象。
接著 b = a 則表示讓變量 b 也同時(shí)指向 1 這個(gè)對(duì)象。Python 的對(duì)象可以被多個(gè)變量所指向(引用)。
最后執(zhí)行 a = a + 1,在這里需要注意的是,Python 的基礎(chǔ)數(shù)據(jù)類型(例如整型(int)、字符串(string)等)是不可變的
所以,a = a + 1,并不是讓 a 的值增加 1,而是表示重新創(chuàng)建了一個(gè)新的值為 2 的對(duì)象,并讓 a 指向它。但是 b 仍然不變,仍然指向 1 這個(gè)對(duì)象。
因此最后的結(jié)果是,a 的值變成了 2,而 b的值不變?nèi)匀皇?nbsp;1。
通過(guò)這個(gè)例子你可以看到,這里的 a 和 b,開始只是兩個(gè)指向同一個(gè)對(duì)象的變量而已,或者你也可以把它們想象成同一個(gè)對(duì)象的兩個(gè)名字。
簡(jiǎn)單的賦值 b = a,并不表示重新創(chuàng)建了新對(duì)象,只是讓同一個(gè)對(duì)象被多個(gè)變量指向或引用。
為什么?
在了解了變量、對(duì)象、引用、賦值之后,我們回到一開始的例子。
a = [1, 2]
a[1] = a
print(a[1])
這段代碼中創(chuàng)建了一個(gè)列表 a,其中包含兩個(gè)元素(1 和 2),然后 a[1] 被賦值為整個(gè)列表 a(a[1] = a),當(dāng)你打印 a[1] 時(shí),它實(shí)際上是指向列表 a 本身。
a = [1, <reference to a>]
這樣就會(huì)導(dǎo)致循環(huán)引用的問(wèn)題。
我們來(lái)分步驟解釋一下這個(gè)過(guò)程:
- a 是一個(gè)包含兩個(gè)元素的列表:[1, 2]。
- a[1] = a 將列表 a 的第二個(gè)元素設(shè)為 a,即a[1]實(shí)際上指向列表 a 本身,形成了一個(gè)循環(huán)引用
- 當(dāng)打印 a[1] 時(shí),Python 發(fā)現(xiàn)這是一個(gè)特殊的情況,即這個(gè)元素是對(duì)列表本身的引用。為了避免無(wú)限循環(huán),Python 會(huì)顯示 ...,表示引用已經(jīng)進(jìn)入了一個(gè)循環(huán)。因此看到的結(jié)果是 [1, [...]]。
那如何避免循環(huán)引用呢?可以使用淺拷貝或者深拷貝來(lái)解決。
我們用淺拷貝來(lái)試一下:
import copy
a = [1, 2]
a[1] = copy.copy(a)
print(a[1])
# 結(jié)果是[1,2]
淺拷貝創(chuàng)建一個(gè)新的對(duì)象,然后將原始對(duì)象中的元素復(fù)制到新對(duì)象中。但是,如果原始對(duì)象的元素是可變對(duì)象(例如列表),那么淺拷貝只會(huì)復(fù)制對(duì)象的引用而不是對(duì)象本身。
就比如上面的例子:
- a = [1, 2] 創(chuàng)建了一個(gè)列表 a,其中有兩個(gè)元素 1 和 2。
- a[1] = copy.copy(a) 將列表 a 的第二個(gè)元素修改為對(duì)列表 a 的淺拷貝。
- 打印 a[1],此時(shí) a[1] 指向了新的對(duì)象 [1, 2]