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