Python中的copy()和deepcopy()

最近在學習支持向量機的時候,看到了淺拷貝(copy)的用法,不是很理解,於是就把相關的知識點做了一下總結。

一、前言

Python關於對象複製有三種方式,直接複製、淺拷貝、深拷貝。不同的複製方式,帶來的效果不同,如果不注意,很有可能出現莫名其妙的Bug。首先先明確幾個概念:

  • 變量:是一個系統表的元素,擁有指向對象的連接空間
  • 對象:被分配的一塊內存,存儲其所代表的的值
  • 引用:是自動形成的從變量到對象的指針
  • 類型:屬於對象,而非指針
  • 不可變對象:一旦創建就不可修改的對象,包括字符串、元組、數值類型
  • 可變對象:可以修改的對象,包括列表、字典、集合

具體來說,對一個不可變對象而言,該對象指向的內存中的值不能被改變。當改變某個值時,由於其所指的值不能被改變,相當於把原來的值複製一份再改變,這會開闢一個新的地址,,變量再指向新的地址。然而對於可變對象而言,該對象指向的內存中值可以改變。變量改變後,實際上是其所指的值發生了改變,並沒有發生了複製行爲,也沒有開闢新的地址。
比如,在這裏插入圖片描述
Python解釋器工作如下:
1.創建變量a
2.創建一個對象(分配一塊內存),來儲存值’python’
3.將變量與對象,通過指針連接起來,從變量到對象的連接稱之爲引用(變量引用對象)
圖示:
在這裏插入圖片描述

二、直接複製

只是複製了新對象的引用,不會開闢新的內存空間,兩個變量指向同一對象(同一內存地址)
對於可變對象:
在這裏插入圖片描述
結果:
在這裏插入圖片描述
對於不可變對象:
在這裏插入圖片描述
結果:
在這裏插入圖片描述
我們可以看到:a和b是一樣的,即這兩個變量地址相同,內容相同。所以,賦值操作(包括對象作爲參數、返回值)不會開闢新的內存空間,只是複製了新對象的引用。修改了a,就會影響b;同理,修改了b就影響a。

三、淺拷貝(copy)

創建新的對象,其內容是原對象的引用,淺拷貝有三種模式:

  • 切片操作:list_b = list_a[:]或list_b = [each for each in list_a]
    在這裏插入圖片描述
    結果:
    在這裏插入圖片描述
  • 工廠函數:list_b = list(list_a)
    在這裏插入圖片描述
    結果:
    在這裏插入圖片描述
  • copy模塊中的copy函數:list_b = copy.copy(list_a)
    在這裏插入圖片描述
    結果:
    在這裏插入圖片描述
    我們可以看到,淺拷貝得到的list_b和list_a是不同的對象,修改list_b不會影響list_a,
    在這裏插入圖片描述
    結果:
    在這裏插入圖片描述
    前方高能,有一個注意的地方來了:
    淺拷貝之所以被稱爲淺拷貝,是它僅僅拷貝了一層,就是拷貝了最外圍的對象本身,內部的元素都只是拷貝了一個引用而已,在list_a中有一個元素是列表[‘Python’,‘C++’],我們把C++修改爲Java,看看發生了什麼:
    在這裏插入圖片描述
    結果:
    在這裏插入圖片描述
    原因總結如下:
    淺拷貝要分兩種情況討論:
  • 當淺拷貝的值是不可變對象(字符串、元組、數值類型)時,和“直接賦值”一樣,對象的“id”值(id()函數用於獲取對象的內存地址)與淺拷貝原來的值相同
  • 當淺拷貝的值是可變對象(列表、字典、集合)時會產生一個“不是那麼獨立的對象”,又分兩種情況:
    1.拷貝的對象中無複雜的子對象,原來的值的改變並不會影響淺拷貝的值,同時淺拷貝的值的改變也不會影響原來的值。
    2.拷貝的對象有複雜子對象時(例如,列表中的一個子元素爲一個列表),如果不改變其中複雜子對象時,淺拷貝的值的改變並不會影響原來的值。但是改變原來的值中的複雜子對象的值會影響淺拷貝的值。

四、深拷貝(deepcopy)

和淺拷貝對應,深拷貝拷貝了對象裏的所有的元素,包括了多層嵌套的元素。深拷貝出來的對象是一個全新的對象,不再與原來的對象有任何的關聯。所以改變原有的被拷貝的對象並不會對新的對象產生影響。因而,它的時間和空間開銷要高。
在這裏插入圖片描述
結果:
在這裏插入圖片描述
同樣對list_a,若使用list_b = copy.deepcopy(list_a),再修改list_b將不會影響到list_a了。即使嵌套的列表具有更深的層次,也不會產生任何影響,因爲深拷貝出來的對象根本就是一個全新的對象,不再與原來的對象有任何關聯。
在這裏插入圖片描述
結果:
在這裏插入圖片描述
如果是不可變對象呢?
在這裏插入圖片描述
結果:
在這裏插入圖片描述
在這裏插入圖片描述
結果:
在這裏插入圖片描述
一句話,對於不可變對象,沒有被拷貝一說,即便是深度拷貝,查出的id也是一樣的,如果對其重新賦值,也只是創建一個對象,替換掉舊的而已。不管是深拷貝,還是淺拷貝,地址值和拷貝後的值是一樣的。
總結如下:

  1. Python中對象的賦值都是進行對象引用(內存地址)的傳遞。
  2. 淺拷貝它拷貝了對象,但對於對象中的元素,依然使用的是原始的引用(內存地址),但所含元素(父對象)的子元素(子對象)不會被拷貝。
  3. 深拷貝可將父對象和子對象進行拷貝。

參考資料:
1.Python中的賦值(複製)、淺拷貝與深拷貝:https://zhuanlan.zhihu.com/p/54011712
2.Python中的淺拷貝和深拷貝:
https://dyfloveslife.github.io/2018/07/30/learning-python-CopyAndDeepCopy/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章