本來給妹子講python-S01E11賦值與對象拷貝機制分析,結果打臉了

本來給妹子講python-S01E11賦值與對象拷貝機制分析,結果打臉了

要點搶先看

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] = 8print(C)print(L)
[8, 3][1, 2, 3, 4, 5]
L = [1,2,3,4,5]C = L[:]C[0] = 8print(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'] = 888print(B)print(D)
{'a': 888, 'b': 2}{'a': 1, 'b': 2}複製代碼

第三種:內置函數list可以生成拷貝

L = [1,2,3,4]C = list(L)C[0] = 888print(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] = 333B[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] = 333B[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 copyL = [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 = L1print(L1 == L2, L1 is L2)print(L1 == L3, L1 is L3)
True FalseTrue
True複製代碼

==測試兩個對象值的相等性,即遞歸的比較所有內嵌對象;

is 測試兩個對象的一致性,即是否是在一個內存空間。

真值問題

Python把任意的空數據結構視為假,把任何非空數據結構視為真

使用None,一般都起到一個空的佔位符的作用,在真值判斷的時候為false

L = [None] * 10print(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'))
TrueTrueFalseTrue複製代碼
本來給妹子講python-S01E11賦值與對象拷貝機制分析,結果打臉了

公眾號二維碼:python數據科學家


分享到:


相關文章: