給妹子講python-S01E11賦值與物件拷貝機制分析

醬油哥在掘金發表於2019-01-16

歡迎關注公眾號:python資料科學家

【要點搶先看】

1.可變物件的原處修改
2.如何獲取物件的獨立複製
3.比較、相等性和真值問題

我們今天的話題要從“可變物件的原處修改”這裡引入,這是一個值得注意的問題。

上一集裡我們談到,賦值操作總是儲存物件的引用,而不是這些物件的拷貝。由於在這個過程中賦值操作會產生相同物件的多個引用,因此我們需要意識到“可變物件”在這裡可能存在的問題:在原處修改可變物件可能會影響程式中其他引用該物件的變數。如果你不想看到這種情景,則你需要明確的拷貝一個物件,而不是簡單賦值。

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)

[NoneNoneNoneNoneNoneNoneNoneNoneNoneNone]
複製程式碼

內建bool函式可以測試一個物件是真還是假

print(bool([1,2]))
print(bool(True))
print(bool(None))
print(bool(`abcde`))

True
True
False
True
複製程式碼

公眾號二維碼:python資料科學家:

給妹子講python-S01E11賦值與物件拷貝機制分析

相關文章