1. 程式人生 > >Python 賦值、淺拷貝、深拷貝的區別?

Python 賦值、淺拷貝、深拷貝的區別?

http://songlee24.github.io/2014/08/15/python-FAQ-02/

在寫Python過程中,經常會遇到物件的拷貝,如果不理解淺拷貝深拷貝的概念,你的程式碼就可能出現一些問題。所以,在這裡按個人的理解談談它們之間的區別。

一、賦值(assignment)

在《Python FAQ1》一文中,對賦值已經講的很清楚了,關鍵要理解變數與物件的關係

1
2
3
4
5
>>> a = [1, 2, 3]
>>> b = a
>>> print(id(a), id(b), sep='\n')
139701469405552
139701469405552

在Python中,用一個變數給另一個變數賦值,其實就是給當前記憶體中的物件增加一個“標籤”而已。

如上例,通過使用內建函式 id() ,可以看出 a 和 b 指向記憶體中同一個物件。a is b會返回 True 。

二、淺拷貝(shallow copy)

注意:淺拷貝和深拷貝的不同僅僅是對組合物件來說,所謂的組合物件就是包含了其它物件的物件,如列表,類例項。而對於數字、字串以及其它“原子”型別,沒有拷貝一說,產生的都是原物件的引用。

所謂“淺拷貝”,是指建立一個新的物件,其內容是原物件中元素的引用。(拷貝組合物件,不拷貝子物件)

常見的淺拷貝有:切片操作、工廠函式、物件的copy()方法、copy模組中的copy函式。

1
2
3
4
5
6
7
8
9
10
>>> a = [1, 2, 3]
>>> b = list(a)
>>> print(id(a), id(b))          # a和b身份不同
140601785066200 140601784764968
>>> for x, y in zip(a, b):       # 但它們包含的子物件身份相同
...     print(id(x), id(y))
... 
140601911441984 140601911441984
140601911442016 140601911442016
140601911442048
140601911442048

從上面可以明顯的看出來,a 淺拷貝得到 b,a 和 b 指向記憶體中不同的 list 物件,但它們的元素卻指向相同的 int 物件。這就是淺拷貝!

三、深拷貝(deep copy)

所謂“深拷貝”,是指建立一個新的物件,然後遞迴的拷貝原物件所包含的子物件。深拷貝出來的物件與原物件沒有任何關聯。

深拷貝只有一種方式:copy模組中的deepcopy函式。

1
2
3
4
5
6
7
8
9
10
11
>>> import copy
>>> a = [1, 2, 3]
>>> b = copy.deepcopy(a)
>>> print(id(a), id(b))
140601785065840 140601785066200
>>> for x, y in zip(a, b):
...     print(id(x), id(y))
... 
140601911441984 140601911441984
140601911442016 140601911442016
140601911442048 140601911442048

看了上面的例子,有人可能會疑惑:

為什麼使用了深拷貝,a和b中元素的id還是一樣呢?

答:這是因為對於不可變物件,當需要一個新的物件時,python可能會返回已經存在的某個型別和值都一致的物件的引用。而且這種機制並不會影響 a 和 b 的相互獨立性,因為當兩個元素指向同一個不可變物件時,對其中一個賦值不會影響另外一個。

我們可以用一個包含可變物件的列表來確切地展示“淺拷貝”與“深拷貝”的區別:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
>>> import copy
>>> a = [[1, 2],[5, 6], [8, 9]]
>>> b = copy.copy(a)              # 淺拷貝得到b
>>> c = copy.deepcopy(a)          # 深拷貝得到c
>>> print(id(a), id(b))           # a 和 b 不同
139832578518984 139832578335520
>>> for x, y in zip(a, b):        # a 和 b 的子物件相同
...     print(id(x), id(y))
... 
139832578622816 139832578622816
139832578622672 139832578622672
139832578623104 139832578623104
>>> print(id(a), id(c))           # a 和 c 不同
139832578518984 139832578622456
>>> for x, y in zip(a, c):        # a 和 c 的子物件也不同
...     print(id(x), id(y))
... 
139832578622816 139832578621520
139832578622672 139832578518912
139832578623104 139832578623392

從這個例子中可以清晰地看出淺拷貝與深拷貝地區別。

總結:

1、賦值:簡單地拷貝物件的引用,兩個物件的id相同。
2、淺拷貝:建立一個新的組合物件,這個新物件與原物件共享記憶體中的子物件。
3、深拷貝:建立一個新的組合物件,同時遞迴地拷貝所有子物件,新的組合物件與原物件沒有任何關聯。雖然實際上會共享不可變的子物件,但不影響它們的相互獨立性。

淺拷貝和深拷貝的不同僅僅是對組合物件來說,所謂的組合物件就是包含了其它物件的物件,如列表,類例項。而對於數字、字串以及其它“原子”型別,沒有拷貝一說,產生的都是原物件的引用。