1. 程式人生 > >python:賦值 | 淺拷貝 | 深拷貝

python:賦值 | 淺拷貝 | 深拷貝

一、賦值--"舊瓶裝舊酒"

在python中,物件的賦值就是簡單的物件引用, 這點和C++等語言不同.如:

In[2]: a = [1, 2, 'hello', ['python', 'C++']]
In[3]: b = a
In[4]: a is b
Out[4]: True
In[5]: b is a
Out[5]: True
In[6]: id(a)
Out[6]: 139705399858952
In[7]: id(b)
Out[7]: 139705399858952
# 總結: 
1.通過 is 判斷它們的記憶體地址相同;
2.通過 id() 來檢視兩者的記憶體地址也相同.
​
In [1]: a = 3                                     
In [2]: b = 3                                     
In [3]: a is b                                     Out[3]: True
In [4]: id(a)                                     Out[4]: 10919488
In [5]: id(b)                                     Out[5]: 10919488
# 通過這個例子,很容易說明:
1. python 中 不可變型別 在記憶體中只有一份, 不論如何巢狀,其id()值相同.

在上述情況下,ab是一樣的,它們指向同一片記憶體地址,b不過是a的別名,是引用.

賦值操作(包括物件作為引數、返回值)不會開闢新的記憶體空間,只是複製了物件的引用. 也就是說 除了 b 這個名字之外, 沒有其它的記憶體開銷. 修改了 a 或 b , 另外一個 b 或 a 同樣跟著受影響.

python的賦值操作還有一個通俗的理解: 先在記憶體中建立等號右邊的物件, 然後把等號左邊的變數作為標籤貼在右邊物件上,是為引用.

二、淺拷貝(shallow copy)--"新瓶裝舊酒"

淺拷貝會建立新物件, 其物件非原來物件的引用, 而是原來物件內第一層物件的引用.

通俗理解:拷貝了引用,並沒有拷貝內容;產生了新物件,但是裡面的內容還是同一份

淺拷貝 三種形式: 切片操作 工廠函式 copy模組中的copy函式. 比如上述列表a;

  • 切片操作: c = a[:]

  • 工程函式: c = list(a)

  • copy函式: c = copy.copy(a)

In[8]: c = a[:]
In[9]: c is a
Out[9]: False
In[10]: id(a)
Out[10]: 139705399858952
In[11]: id(c)
Out[11]: 139705390811656
In[12]: [id(x) for x in a]
Out[12]: [10919424, 10919456, 139705494889056, 139705399859272]
In[13]: [id(x) for x in c]
Out[13]: [10919424, 10919456, 139705494889056, 139705399859272]
# 總結:
1. b 不在(is)是 a ,也不指向(id())同一個記憶體空間;
2. 但是 [id(x) for x in a] 和 [id(x) for x in c] 結果相同. 說明: a 和 c 內部的元素物件指向的是同一片記憶體地址.
    
In[14]: id(a[3][0])
Out[14]: 139705495480280
In[15]: id(c[3][0])
Out[15]: 139705495480280
In[16]: a[3][0]
Out[16]: 'python'
In[17]: a[3].append('java')
In[18]: b
Out[18]: [1, 2, 'hello', ['python', 'C++', 'java']]
# 總結:
1. 關於淺copy的理解,個人以為用內層和外層來區分更容易理解.通俗來說,淺拷貝就是隻修改了最外層的引用, 使得元素最外層的地址變化,但是對原來元素的內層地址和引用均未做修改.
2. 把這裡最外層列表中的每一個元素(包括列表元素)都看作一個'坑', 每個'坑'再指向一個具體的 物件. 這就是'引用'.
​
In[19]: a[1] = 10
In[20]: a
Out[20]: [1, 10, 'hello', ['python', 'C++', 'java']]
In[21]: c
Out[21]: [1, 2, 'hello', ['python', 'C++', 'java']]
In[22]: b
Out[22]: [1, 10, 'hello', ['python', 'C++', 'java']]
 
# 事實上,這裡就比較繞; 思路還是要從 是否修改引用 來思考.
1. python中一直都是先在記憶體中有物件,然後再對物件有引用.
2. [17]修改的是 巢狀列表內部的元素,並未對最裡層列表本身的 記憶體地址 產生影響, 所以 a 和 c 指向的內層列表仍然是同一個物件,所以其中一個修改,兩者同時發生改變.
3. [19]修改的是 不可變型別, 相當於 指向了新的引用;所以 a 和 c 不同.

三、深拷貝(deep copy)--"新瓶裝新酒"

深拷貝 只有一種形式 , copy模組中的 deepcopy() 函式

深拷貝拷貝了物件的所有元素,包括多層巢狀的元素. 因此,它的時間和空間開銷要高.

深拷貝拷貝出來的物件根本就是一個全新的物件,不再於原來的物件有任何的關聯.

深拷貝類似沒有 出口的遞迴拷貝

四、拷貝注意點

對於非容器型別, 如數字 , 字元 , 以及其它的"原子"型別, 沒有拷貝一說, 產生的都是原物件的引用.

如果元組變數值包含原子型別物件, 即使採用了深拷貝,也只能得到淺拷貝.