1. 程式人生 > >python引用和對象詳解

python引用和對象詳解

同時 hang () 引用值 垃圾回收 列表項 實驗 就是 因此

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引用和對象詳解