1. 程式人生 > 其它 >對 Python 中一切皆物件的重新認知

對 Python 中一切皆物件的重新認知

在使用 Python 列表時, 出現了修改其中一個列表, 其他列表聯動改變這個情況, 在查閱文件後, 寫在這裡記錄一下.

出現這個情況的原因是因為我使用了 a = b 這個賦值語句, 我下意識的認為 a, b 是兩個不同的物件, 其實在 Python 一切都是物件, 所謂的變數, 只是指向一個物件的記憶體地址. 我們平時使用的賦值語句 = 其實只是讓一個一個變數指向另一個變數指向的記憶體地址. 例如:

a = 1
b = a
print(a, b)
print(id(a))
print(id(b))
a = 2
print(a, b)

1 1
140712422177856
140712422177856
2 1

可以看到兩個變數指向同一個記憶體地址, 這是 Python 直譯器為了節省記憶體資源, 選擇的一種方式. 可以看到, 當變數指向不可變型別時候, b 的值並不會隨著 a 的變化而變化.

然而這種方式有一個弊端, 當我們面對可變型別時, 就會發生一些不可控的事情.

a = [1, 2, 3]
b = a
print(a, b)
print(id(a))
print(id(b))
a[0] = 4
print(a, b)

[1, 2, 3] [1, 2, 3]
2537876904712
2537876904712
[4, 2, 3] [4, 2, 3]

可以看到, 因為 a, b 兩個變數指向的是同一個記憶體地址, 當我們修改 a 的資料時, b會一起改變. 常規情況下,這並不是我們想要的結果.

那麼, 有沒有辦法把資料拷貝到一塊新的記憶體地址中呢?

淺拷貝與深拷貝

這就需要用到 Python 內建庫 copy. 我們需要用到這個庫中的 copy, deepcopy 兩個方法.

淺拷貝

我們使用 b = copy.copy(a) 進行淺拷貝, 執行這個語句後 a, b 是兩個不同的物件, 指向不同的記憶體地址.

但是 a, b 中的子物件還是會指向同一地址, 例如下文中的 a 列表中的字典, b 列表中的字典就是子物件, 他們其實指向的是同一記憶體地址.

import copy


a = [1, 2, 3, {'a': 1, 'b': 2, 'c': 3}]
b = copy.copy(a)
print(a)
print(b)
print(id(b))
print(id(a))
a[0] = 2
print(a)
print(b)

[1, 2, 3, {'a': 1, 'b': 2, 'c': 3}]
[1, 2, 3, {'a': 1, 'b': 2, 'c': 3}]
2537876907720
2537876907400
[2, 2, 3, {'a': 1, 'b': 2, 'c': 3}]
[1, 2, 3, {'a': 1, 'b': 2, 'c': 3}]

可以看到, a, b 指向了不同的地址. 我們看下 a, b 中的子物件指向的記憶體地址

import copy


a = [1, 2, 3, {'a': 1, 'b': 2, 'c': 3}]
b = copy.copy(a)
print(id(a[3]))
print(id(b[3]))

2537876900904
2537876900904

可以看到, a, b 的子物件指向的記憶體. 因此, 當我們對這個字典進行操作時, 另一個數組物件中的也會隨之變化, 原理同上.

那麼, 有沒有什麼可以規避這一現象呢? 接下來我們走進深拷貝.

深拷貝

我們使用 b = copy.deepcopy(a) 進行深拷貝, 執行這個語句後 a, b 是兩個不同的物件, 指向不同的記憶體地址. 且 a, b 的子物件也指向不同的記憶體地址, a, b 指向除了值相等, 完全沒有任何關係的物件.

import copy


a = [1, 2, 3, {'a': 1, 'b': 2, 'c': 3, 'f': [1, 2, 3]}]
b = copy.deepcopy(a)
print(a)
print(b)
print(id(b))
print(id(a))
print(id(a[3]))
print(id(b[3]))
print(id(a[3]["f"]))
print(id(b[3]["f"]))
a[1] = 0
a[3]['d'] = 4
print(a)
print(b)

[1, 2, 3, {'a': 1, 'b': 2, 'c': 3, 'f': [1, 2, 3]}]
[1, 2, 3, {'a': 1, 'b': 2, 'c': 3, 'f': [1, 2, 3]}]
2537876918920
2537876918728
2537876851592
2537876851672
2537876916296
2537876919304
[1, 0, 3, {'a': 1, 'b': 2, 'c': 3, 'f': [1, 2, 3], 'd': 4}]
[1, 2, 3, {'a': 1, 'b': 2, 'c': 3, 'f': [1, 2, 3]}]

可以看到, 不論巢狀物件有多少個子物件, 進行深拷貝後得到的物件及其子物件, 都指向不同的地址. 同樣的, 對其中任何資料的修改都不會影響到另一個物件.

可以看到, Python 為了適應不同的需求, 為我們提供了非常靈活的方法, 可以根據需求靈活使用.