Last Updated on 2021-10-05 by Clay
在 Python 中最容易導致程式錯誤的原因之一,就是 List 物件內值的意外改變。很多人剛遇到這個問題,臉上的表情就好像見鬼了一般,可能還會嚷嚷 Python 壞掉了也不一定 XD
一個最最簡單的例子,就是 List 物件跟著賦予的來源一起進行了更動。以下看個簡單的範例:
# coding: utf-8 def main(): # Init a = [[1, 1], [2, 2]] b = a # Append a.append([3, 3]) # Print print('a:', a) print('b:', b) if __name__ == '__main__': main()
Output:
a: [[1, 1], [2, 2], [3, 3]]
b: [[1, 1], [2, 2], [3, 3]]
很奇怪,對吧?明明增加了新值的只有 a 變數啊!
這個原因其實很單純,因為 b 只是一個共享 a 值的新變數名稱罷了。我們可以使用 id()
函式瞧瞧:
# coding: utf-8 def main(): # Init a = [[1, 1], [2, 2]] b = a # Append a.append([3, 3]) # Print print('a id:', id(a)) print('b id:', id(b)) if __name__ == '__main__': main()
Output:
a id: 140219101052168
b id: 140219101052168
會發現,a 和 b 真的是同樣的值,只是變數名稱不一樣罷了。
但這很麻煩啊,通常我們撰寫程式並不希望使用兩個同樣值卻不一樣名稱的變數,而是希望 b 能『複製』a 的值,但以後不論增減值,a 和 b 都是毫不相關的。
這時候,就輪到 Python 中的 copy 模組登場了。
淺複製(Shallow)與深複製(Deep)
Python 的 copy 模組中,有分成淺複製(shallow copy)以及深複製(deep copy)兩類。
淺複製(shallow copy)
『淺複製』使用 copy()
函式,能夠讓複製的物件獨立,也就是 a 和 b 已經無關了,新增新的值也不會影響到 b。
# coding: utf-8 import copy def main(): # Init a = [[1, 1], [2, 2]] b = copy.copy(a) # Change a.append([3, 3]) # Print print('a:', a) print('a id:', id(a)) print('='*50) print('b:', b) print('b id:', id(b)) if __name__ == '__main__': main()
Output:
a: [[1, 1], [2, 2], [3, 3]]
a id: 140378660764744
==================================================
b: [[1, 1], [2, 2]]
b id: 140378660765000
但是淺複製跟深複製還是不一樣的。也就是雖然 a 變數新增、刪減元素不會影響到 b,但若是直接更動 a 變數內 b 已經參考的元素,則還是會對 b 產生影響。
# coding: utf-8 import copy def main(): # Init a = [[1, 1], [2, 2]] b = copy.copy(a) # Change a[0][0] = 0 # Print print('a:', a) print('a id:', id(a)) print('='*50) print('b:', b) print('b id:', id(b)) if __name__ == '__main__': main()
Output:
a: [[0, 1], [2, 2]]
a id: 140223083543624
==================================================
b: [[0, 1], [2, 2]]
b id: 140223083543880
可以看到, [1, 1] 的第一個元素都被改變了。要防止這種事情發生,只能使用 deepcopy()
。
深複製(deep copy)
『深複製』使用 deepcopy()
函式,能讓 a 和 b 兩個變數完全分離,是徹徹底底地複製。
# coding: utf-8 import copy def main(): # Init a = [[1, 1], [2, 2]] b = copy.deepcopy(a) # Change a.append([3, 3]) a[0][0] = 0 # Print print('a:', a) print('a id:', id(a)) print('='*50) print('b:', b) print('b id:', id(b)) if __name__ == '__main__': main()
Output:
a: [[0, 1], [2, 2], [3, 3]]
a id: 140719924018248
==================================================
b: [[1, 1], [2, 2]]
b id: 140719924018504
可以看到,這次就算修改 a 中的元素也不會影響到 b 了。
References
- https://www.programiz.com/python-programming/shallow-deep-copy
- https://www.python-course.eu/deep_copy.php
- https://realpython.com/copying-python-objects/