python引用和對象詳解
python引用和對象詳解
@[馬克飛象]
python中變量名和對象是分離的
例子 1:
a = 1
這是一個簡單的賦值語句,整數 1 為一個對象,a 是一個引用,利用賦值語句,引用a指向了對象1.
例子 2:
>>> a = 1
>>> id(a)
24834392
>>> a = ‘banana‘
>>> id(a)
139990659655312
第一個語句中, 2是儲存在內存中的一個整數對象,通過賦值 引用a 指向了 對象 1
第二個語句中,內存中建立了一個字符串對象‘banana’,通過賦值 將 引用a 指向了 ‘banana’,同時,對象1不在有引用指向它,它會被python的內存處理機制給當我垃圾回收,釋放內存。
例子 3:
>>> a = 3
>>> b = 3
>>> id(a)
10289448
>>> id(b)
10289448
在這裏可以看到 這倆個引用 指向了同一個 對象,這是為什麽呢? 這個跟python的內存機制有關系,因為對於語言來說,頻繁的進行對象的銷毀和建立,特別浪費性能。所以在Python中,整數和短小的字符,Python都會緩存這些對象,以便重復使用。
例子 4:
>>> a = 4 >>> b = a >>> id(a) 36151568 >>> id(b) 36151568 >>> a = a+2 >>> id(a) 36151520 >>> id(b) 36151568
可以看到 a 的引用改變了,但是 b 的引用未發生改變;a,b指向不同的對象; 第3句對 a 進行了重新賦值,讓它指向了新的 對象6;即使是多個引用指向同一個對象,如果一個引用值發生變化,那麽實際上是讓這個引用指向一個新的引用,並不影響其他的引用的指向。從效果上看,就是各個引用各自獨立,互不影響。
例子 5:
>>> L1 = [1,2,3] >>> L2 = L1 >>> id(L1) 139643051219496 >>> id(L2) 139643051219496 >>> L1[0] = 10 >>> id(L1) 139643051219496 >>> id(L2) 139643051219496 >>> L2 [10, 2, 3]
同樣的跟第四個例子那樣,修改了其中一個對象的值,但是可以發現 結果 並不與 第四個栗子那樣, 在本次實驗中,L1 和 L2 的引用沒有發生任何變化,但是 列表對象[1,2,3] 的值 變成了 [10,2,3](列表對象改變了)
在該情況下,我們不再對L1這一引用賦值,而是對L1所指向的表的元素賦值。結果是,L2也同時發生變化。
原因何在呢?因為L1,L2的指向沒有發生變化,依然指向那個表。表實際上是包含了多個引用的對象(每個引用是一個元素,比如L1[0],L1[1]..., 每個引用指向一個對象,比如1,2,3), 。而L1[0] = 10這一賦值操作,並不是改變L1的指向,而是對L1[0], 也就是表對象的一部份(一個元素),進行操作,所以所有指向該對象的引用都受到影響。
(與之形成對比的是,我們之前的賦值操作都沒有對對象自身發生作用,只是改變引用指向。)
列表可以通過引用其元素,改變對象自身(in-place change)。這種對象類型,稱為可變數據對象(mutable object),詞典也是這樣的數據類型。
而像之前的數字和字符串,不能改變對象本身,只能改變引用的指向,稱為不可變數據對象(immutable object)。
我們之前學的元組(tuple),盡管可以調用引用元素,但不可以賦值,因此不能改變對象自身,所以也算是immutable object.
例子 6:
l = [1,2,3]
for item in l:
item = 8
# 這裏只是讓item指向l[i]所指向的對象,item = 8,則item指向新的對象8,不改變l[i]
print(l) # [1,2,3]
for i in range(len(l)):
l[i] = 8
# 這裏令l[i]指向新的對象 8
print(l) # [8,8,8]
例子 7:
l1 = []
a = 0
for i in range(1,5):
a = i
l1.append(a) # 添加的是a指向的對象
print(l1) # [1, 2, 3, 4]
l2 = []
b = [1,2,3]
for i in range(1,5):
b[1] = i
l2.append(b) # 添加的是b指向的對象,它包括列表元素的引用,列表本身沒有改變,只是列表項[1]指向的對象變了
print(l2) # [[1, 4, 3], [1, 4, 3], [1, 4, 3], [1, 4, 3]]
# 不是預料的 [[1, 1, 3], [1, 2, 3], [1, 3, 3], [1, 4, 3]]
可以參考例子5。所以,每次列表實際上都是添加同一個對象。
l2 = []
b = [1,2,3]
for i in range(1,5):
b[1] = i
l2.append(copy.copy(b))
# l2.append(copy.deepcopy(b)) 和copy.copy()結果一樣
print(l2) # [[1, 1, 3], [1, 2, 3], [1, 3, 3], [1, 4, 3]]
copy.copy() 淺拷貝。只拷貝父對象,不會拷貝對象的內部的子對象。
那麽,copy.copy()和copy.deepcopy()有什麽區別呢?
l2 = []
b = [1,[4,5],3]
for i in range(1,5):
b[1][0] = i
l2.append(copy.copy(b)) # [[1, [4, 5], 3], [1, [4, 5], 3], [1, [4, 5], 3], [1, [4, 5], 3]]
# l2.append(copy.deepcopy(b)) # [[1, [1, 5], 3], [1, [2, 5], 3], [1, [3, 5], 3], [1, [4, 5], 3]]
print(l2)
copy.deepcopy() 深拷貝 拷貝對象及其子對象
例子 8:
函數的參數傳遞探究
def testPara(aList):
aList[0] = 8
print("inside function",aList) # [8, 2, 3]
if __name__ == ‘__main__‘:
l1 = [1,2,3]
testPara(l1)
print(l1) # [8, 2, 3]
def testPara(aList):
aList = [1,2]
print("inside function",aList) # [1,2] aList重新指向一個新的對象
if __name__ == ‘__main__‘:
l1 = [1,2,3]
testPara(l1)
print(l1) # [1, 2, 3]
這裏要想直接把l1在函數內改變,可以在函數return l1
def test(l:list):
l.pop(0)
if __name__ == ‘__main__‘:
l = [1,2,3]
test(l)
print(l) # [2,3]
python引用和對象詳解