python:賦值 | 淺拷貝 | 深拷貝
阿新 • • 發佈:2018-12-16
一、賦值--"舊瓶裝舊酒"
在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()值相同.
在上述情況下,a
和b
是一樣的,它們指向同一片記憶體地址,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()
函式
深拷貝拷貝了物件的所有元素,包括多層巢狀的元素. 因此,它的時間和空間開銷要高.
深拷貝拷貝出來的物件根本就是一個全新的物件,不再於原來的物件有任何的關聯.
深拷貝類似沒有 出口的遞迴拷貝
四、拷貝注意點
對於非容器型別, 如數字 , 字元 , 以及其它的"原子"型別, 沒有拷貝一說, 產生的都是原物件的引用.
如果元組變數值包含原子型別物件, 即使採用了深拷貝,也只能得到淺拷貝.