我們今天的話題要從“可變對象的原處修改”這裡引入,這是一個值得注意的問題。
上一集裡我們談到,指派操作總是存儲對象的引用,而不是這些對象的拷貝。由于在這個過程中指派操作會産生相同對象的多個引用,是以我們需要意識到“可變對象”在這裡可能存在的問題:在原處修改可變對象可能會影響程式中其他引用該對象的變量。如果你不想看到這種情景,則你需要明确的拷貝一個對象,而不是簡單指派。
X = [1,2,3,4,5]
L = ['a', X, 'b']
D = {'x':X, 'y':2}
print(L)
print(D)
['a', [1, 2, 3, 4, 5], 'b']
{'y': 2, 'x': [1, 2, 3, 4, 5]}
在這個例子中,我們可以看到清單[1,2,3,4,5]有三個引用,被變量X引用、被清單L内部元素引用、被字典D内部元素引用。那麼利用這三個引用中的任意一個去修改清單[1,2,3,4,5],也會同時改變另外兩個引用的對象,例如我利用L來改變[1,2,3,4,5]的第二個元素,運作的結果就非常明顯。
X = [1,2,3,4,5]
L = ['a', X, 'b']
D = {'x':X, 'y':2}
L[1][2] = 'changed'
print(X)
print(L)
print(D)
[1, 2, 'changed', 4, 5]
['a', [1, 2, 'changed', 4, 5], 'b']
{'x': [1, 2, 'changed', 4, 5], 'y': 2}
【妹子說】有坑請繞行呀,在這些地方還真的挺容易犯錯的。
引用是其他語言中指針的更高層的模拟。他可以幫助你在程式範圍内任何地方傳遞大型對象而不必在途中産生拷貝,起到優化程式的作用。
【妹子說】可是如果我不想共享對象引用,而是想實實在在擷取對象的一份獨立的複制,該怎麼辦呢?
能想到這一層确實很不錯,其實這個很簡單,常用的手法有以下幾種:
第一種方法:分片表達式能傳回一個新的對象拷貝,沒有限制條件的分片表達式能夠完全複制清單
L = [1,2,3,4,5]
C = L[1:3]
C[0] = 8
print(C)
print(L)
[8, 3]
[1, 2, 3, 4, 5]
L = [1,2,3,4,5]
C = L[:]
C[0] = 8
print(C)
print(L)
[8, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
可以看出,用分片表達式得到了新的清單拷貝C,對這個清單進行修改,不會改變原始清單L的值。
第二種方法:字典的copy方法也能夠實作字典的完全複制:
D = {'a':1, 'b':2}
B = D.copy()
B['a'] = 888
print(B)
print(D)
{'a': 888, 'b': 2}
{'a': 1, 'b': 2}
第三種:内置函數list可以生成拷貝
L = [1,2,3,4]
C = list(L)
C[0] = 888
print(C)
print(L)
[888, 2, 3, 4]
[1, 2, 3, 4]
最後我們看一個複雜一些的例子
B通過無限制條件的分片操作得到了A清單的拷貝,B對清單内元素本身的修改,不會影響到A,例如修改數值,例如把引用換成别的清單引用:
L = [1,2,3,4]
A = [1,2,3,L]
B = A[:]
B[1] = 333
B[3] = ['888','999']
print(B)
print(A)
print(L)
[1, 333, 3, ['888', '999']]
[1, 2, 3, [1, 2, 3, 4]]
[1, 2, 3, 4]
但是如果是這種場景呢?
L = [1,2,3,4]
A = [1,2,3,L]
B = A[:]
B[1] = 333
B[3][1] = ['changed']
print(B)
print(A)
print(L)
[1, 333, 3, [1, ['changed'], 3, 4]]
[1, 2, 3, [1, ['changed'], 3, 4]]
[1, ['changed'], 3, 4]
因為B的最後一個元素也是清單L的引用(可以看做擷取了L的位址),是以通過這個引用對所含清單對象元素進行進一步的修改,也會影響到A,以及L本身
是以說,無限制條件的分片操作以及字典的copy方法隻能進行頂層的指派。就是在最頂層,如果是數值對象就複制數值,如果是對象引用就直接複制引用,是以仍然存在下一級潛藏的共享引用現象。
如果想實作自頂向下,深層次的将每一個層次的引用都做完整獨立的複制,那麼就要使用copy子產品的deepcopy方法。
import copy
L = [1,2,3,4]
A = [1,2,3,L]
B = copy.deepcopy(A)
B[3][1] = ['changed']
print(B)
print(A)
print(L)
[1, 2, 3, [1, ['changed'], 3, 4]]
[1, 2, 3, [1, 2, 3, 4]]
[1, 2, 3, 4]
這樣,就實作了遞歸的周遊對象來複制他所有的組成成分,實作了完完全全的拷貝,彼此之間再無瓜葛。
【妹子說】嗯,真沒想到簡單的指派還有這麼多的坑!我自己來總結總結:普通的=指派得到的其實僅僅是共享引用;無限條件的分片、字典copy方法和内置函數list這三種方法可以進行頂層對象的拷貝,而deepcopy可以徹底的實作自頂向下的完全拷貝。是以我們在實際使用的時候,一定要考慮都這些巨大的差别呀!
最後,再補充一個很小的知識:python中的比較、相等性和真值問題。
L1 = [1,2,['A','B']]
L2 = [1,2,['A','B']]
L3 = L1
print(L1 == L2, L1 is L2)
print(L1 == L3, L1 is L3)
True False
True True
==測試兩個對象值的相等性,即遞歸的比較所有内嵌對象;
is 測試兩個對象的一緻性,即是否是在一個記憶體空間。
真值問題
Python把任意的空資料結構視為假,把任何非空資料結構視為真
使用None,一般都起到一個空的占位符的作用,在真值判斷的時候為false
L = [None] * 10
print(L)
[None, None, None, None, None, None, None, None, None, None]
内置bool函數可以測試一個對象是真還是假
print(bool([1,2]))
print(bool(True))
print(bool(None))
print(bool('abcde'))
True
True
False
True
原文釋出時間為:2018-08-15
本文作者:給妹子講python
本文來自雲栖社群合作夥伴“
Python愛好者社群”,了解相關資訊可以關注“
”。