自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

一文了解Python深拷貝與淺拷貝問題

開發(fā) 后端
在平時工作中,經(jīng)常涉及到數(shù)據(jù)的傳遞,在數(shù)據(jù)傳遞使用過程中,可能會發(fā)生數(shù)據(jù)被修改的問題。為了防止數(shù)據(jù)被修改,就需要在傳遞一個副本,即使副本被修改,也不會影響原數(shù)據(jù)的使用。為了生成這個副本,就產(chǎn)生了拷貝。

在平時工作中,經(jīng)常涉及到數(shù)據(jù)的傳遞,在數(shù)據(jù)傳遞使用過程中,可能會發(fā)生數(shù)據(jù)被修改的問題。為了防止數(shù)據(jù)被修改,就需要在傳遞一個副本,即使副本被修改,也不會影響原數(shù)據(jù)的使用。為了生成這個副本,就產(chǎn)生了拷貝。今天就說一下Python中的深拷貝與淺拷貝的問題。

概念普及:對象、可變類型、引用

數(shù)據(jù)拷貝會涉及到Python中對象、可變類型、引用這3個概念,先來看看這幾個概念,只有明白了他們才能更好的理解深拷貝與淺拷貝到底是怎么一回事。

Python對象

在Python中,對對象有一種很通俗的說法,萬物皆對象。說的就是構造的任何數(shù)據(jù)類型都是一個對象,無論是數(shù)字,字符串,還是函數(shù),甚至是模塊,Python都對當做對象處理。

所有Python對象都擁有三個屬性:身份、類型、值。

看一個簡單的例子:

  1. In [1]: name = "laowang" # name對象 
  2. In [2]: id(name) # id:身份的唯一標識 
  3. Out[2]: 1698668550104 
  4. In [3]: type(name) # type:對象的類型,決定了該對象可以保存什么類型的值 
  5. Out[3]: str 
  6. In [4]: name # 對象的值,表示的數(shù)據(jù) 
  7. Out[4]: 'laowang' 

可變與不可變對象

在Python中,按更新對象的方式,可以將對象分為2大類:可變對象與不可變對象。

  • 可變對象: 列表、字典、集合
  • 所謂可變是指可變對象的值可變,身份是不變的。
  • 不可變對象:數(shù)字、字符串、元組
  • 不可變對象就是對象的身份和值都不可變。新創(chuàng)建的對象被關聯(lián)到原來的變量名,舊對象被丟棄,垃圾回收器會在適當?shù)臅r機回收這些對象。
  1. In [7]: var1 = "python" 
  2. In [8]: id(var1) 
  3. Out[8]: 1700782038408 
  4. #由于var1是不可變的,重新創(chuàng)建了java對象,隨之id改變,舊對象python會在某個時刻被回收 
  5. In [9]: var1 = "java" 
  6. In [10]: id(var1)  
  7. Out[10]: 1700767578296 

引用

在 Python 程序中,每個對象都會在內(nèi)存中申請開辟一塊空間來保存該對象,該對象在內(nèi)存中所在位置的地址被稱為引用。在開發(fā)程序時,所定義的變量名實際就對象的地址引用。

引用實際就是內(nèi)存中的一個數(shù)字地址編號,在使用對象時,只要知道這個對象的地址,就可以操作這個對象,但是因為這個數(shù)字地址不方便在開發(fā)時使用和記憶,所以使用變量名的形式來代替對象的數(shù)字地址。 在 Python 中,變量就是地址的一種表示形式,并不開辟開辟存儲空間。

就像 IP 地址,在訪問網(wǎng)站時,實際都是通過 IP 地址來確定主機,而 IP 地址不方便記憶,所以使用域名來代替 IP 地址,在使用域名訪問網(wǎng)站時,域名被解析成 IP 地址來使用。

通過一個例子來說明變量和變量指向的引用就是一個東西。

  1. In [11]: age = 18 
  2. In [12]: id(age) 
  3. Out[12]: 1730306752 
  4. In [13]: id(18) 
  5. Out[13]: 1730306752 

逐步深入:引用賦值

上邊已經(jīng)明白,引用就是對象在內(nèi)存中的數(shù)字地址編號,變量就是方便對引用的表示而出現(xiàn)的,變量指向的就是此引用。賦值的本質(zhì)就是讓多個變量同時引用同一個對象的地址。 那么在對數(shù)據(jù)修改時會發(fā)生什么問題呢?

  • 不可變對象的引用賦值。
  • 對不可變對象賦值,實際就是在內(nèi)存中開辟一片空間指向新的對象,原不可變對象不會被修改。

原理圖如下:

下面通過案例來理解一下:

a與b在內(nèi)存中都是指向1的引用,所以a、b的引用是相同的。

  1. In [1]: a = 1 
  2. In [2]: b = a 
  3. In [3]: id(a) 
  4. Out[3]: 1730306496 
  5. In [4]: id(b) 
  6. Out[4]: 1730306496 

現(xiàn)在再給a重新賦值,看看會發(fā)生什么變化?

從下面不難看出:當給a 賦新的對象時,將指向現(xiàn)在的引用,不在指向舊的對象引用。

  1. In [1]: a = 1 
  2. In [2]: b = a 
  3. In [5]: a = 2 
  4. In [6]: id(a) 
  5. Out[6]: 1730306816 
  6. In [7]: id(b) 
  7. Out[7]: 1730306496 
  • 可變對象的引用賦值。
  • 可變對象保存的并不是真正的對象數(shù)據(jù),而是對象的引用。當對可變對象進行賦值時,只是將可變對象中保存的引用指向了新的對象。

原理圖如下:

仍然通過一個實例來體會一下,可變對象引用賦值的過程。

當改變l1時,整個列表的引用會指新的對象,但是l1與l2都是指向保存的同一個列表的引用,所以引用地址不會變。

  1. In [3]: l1 = [1, 2, 3] 
  2. In [4]: l2 = l1 
  3. In [5]: id(l1) 
  4. Out[5]: 1916633584008 
  5. In [6]: id(l2) 
  6. Out[6]: 1916633584008 
  7. In [7]: l1[0] = 11 
  8. In [8]: id(l1) 
  9. Out[8]: 1916633584008 
  10. In [9]: id(l2) 
  11. Out[9]: 1916633584008 

主旨詳解:淺拷貝、深拷貝

經(jīng)過前2部分的解讀,大家對對象的引用賦值應該有了一個清晰的認識了。

下面大家思考一個這樣的問題:Python中如何解決原始數(shù)據(jù)在函數(shù)傳遞之后不受影響了?

這個問題Python已經(jīng)幫我們解決了,使用對象的拷貝或者深拷貝就可以愉快的解決了。

下面具體來看看Python中的淺拷貝與深拷貝是如何實現(xiàn)的。

  • 淺拷貝:

為了解決函數(shù)傳遞后被修改的問題,就需要拷貝一份副本,將副本傳遞給函數(shù)使用,就算是副本被修改,也不會影響原始數(shù)據(jù) 。

不可變對象的拷貝

不可變對象只在修改的時候才會在內(nèi)存中開辟新的空間, 而拷貝實際上是讓多個對象同時指向一個引用,和對象的賦值沒區(qū)別。

同樣的,通過一個實例來感受一下:不難看出,a與b指向相同的引用,不可變對象的拷貝就是對象賦值。

  1. In [11]: import copy 
  2. In [12]: a = 10 
  3. In [13]: b = copy.copy(a) 
  4. In [14]: id(a) 
  5. Out[14]: 1730306496 
  6. In [15]: id(b) 
  7. Out[15]: 1730306496 

可變對象的拷貝

對于不可變對象的拷貝,對象的引用并沒有發(fā)生變化,那么可變對象的拷貝會不會和不可變對象一樣了?我們接著往下看。

通過下面這個實例可以看出:可變對象的拷貝,會在內(nèi)存中開辟一個新的空間來保存拷貝的數(shù)據(jù)。當再改變之前的對象時,對拷貝之后的對象沒有任何影響。

  1. In [24]: import copy 
  2. In [25]: l1 = [1, 2, 3] 
  3. In [26]: l2 = copy.copy(l1) 
  4. In [27]: id(l1) 
  5. Out[27]: 1916631742088 
  6. In [28]: id(l2) 
  7. Out[28]: 1916636282952 
  8. In [29]: l1[0] = 11 
  9. In [30]: id(l1) 
  10. Out[30]: 1916631742088 
  11. In [31]: id(l2) 
  12. Out[31]: 1916636282952 

原理圖如下:

現(xiàn)在再回到剛才那個問題,是不是淺拷貝就可以解決原始數(shù)據(jù)在函數(shù)傳遞之后不變的問題了?下面看一個稍微復雜一點的數(shù)據(jù)結構。

通過下面這個實例可以發(fā)現(xiàn):復雜對象在拷貝時,并沒有解決數(shù)據(jù)在傳遞之后,數(shù)據(jù)改變的問題。 出現(xiàn)這種原因,是copy() 函數(shù)在拷貝對象時,只是將指定對象中的所有引用拷貝了一份,如果這些引用當中包含了一個可變對象的話,那么數(shù)據(jù)還是會被改變。 這種拷貝方式,稱為淺拷貝。

  1. In [35]: a = [1, 2] 
  2. In [36]: l1 = [3, 4, a] 
  3. In [37]: l2 = copy.copy(l1) 
  4. In [38]: id(l1) 
  5. Out[38]: 1916631704520 
  6. In [39]: id(l2) 
  7. Out[39]: 1916631713736 
  8. In [40]: a[0] = 11 
  9. In [41]: id(l1) 
  10. Out[41]: 1916631704520 
  11. In [42]: id(l2) 
  12. Out[42]: 1916631713736 
  13. In [43]: l1 
  14. Out[43]: [3, 4, [11, 2]] 
  15. In [44]: l2 
  16. Out[44]: [3, 4, [11, 2]] 

原理圖如下:

對于上邊這種狀況,Python還提供了另一種拷貝方式(深拷貝)來解決。

  • 深拷貝

區(qū)別于淺拷貝只拷貝頂層引用,深拷貝會逐層進行拷貝,直到拷貝的所有引用都是不可變引用為止。

接下來我們看看,要是將上邊的拷貝實例用使用深拷貝的話,原始數(shù)據(jù)改變的問題還會不會存在了?

下面的實例清楚的告訴我們:之前的問題就可以***解決了。

  1. import copy 
  2. l1 = [3, 4, a] 
  3. In [47]: l2 = copy.deepcopy(li) 
  4. In [48]: id(l1) 
  5. Out[48]: 1916632194312 
  6. In [49]: id(l2) 
  7. Out[49]: 1916634281416 
  8. In [50]: a[0] = 11 
  9. In [51]: id(l1) 
  10. Out[51]: 1916632194312 
  11. In [52]: id(l2) 
  12. Out[52]: 1916634281416 
  13. In [54]: l1 
  14. Out[54]: [3, 4, [11, 2]] 
  15. In [55]: l2 
  16. Out[55]: [1, 2, 3] 

原理圖如下:

查漏補缺

為什么Python默認的拷貝方式是淺拷貝?

  • 時間角度:淺拷貝花費時間更少。
  • 空間角度:淺拷貝花費內(nèi)存更少。
  • 效率角度:淺拷貝只拷貝頂層數(shù)據(jù),一般情況下比深拷貝效率高。

本文知識點總結:

  • 不可變對象在賦值時會開辟新空間。
  • 可變對象在賦值時,修改一個的值,另一個也會發(fā)生改變。
  • 深、淺拷貝對不可變對象拷貝時,不開辟新空間,相當于賦值操作。
  • 淺拷貝在拷貝時,只拷貝***層中的引用,如果元素是可變對象,并且被修改,那么拷貝的對象也會發(fā)生變化。
  • 深拷貝在拷貝時,會逐層進行拷貝,直到所有的引用都是不可變對象為止。
  • Python 中有多種方式實現(xiàn)淺拷貝,copy模塊的copy 函數(shù) ,對象的 copy 函數(shù) ,工廠方法,切片等。
  • 大多數(shù)情況下,編寫程序時,都是使用淺拷貝,除非有特定的需求。
  • 淺拷貝的優(yōu)點:拷貝速度快,占用空間少,拷貝效率高。
責任編輯:武曉燕 來源: 今日頭條
相關推薦

2020-06-23 08:41:47

JavaScript開發(fā)技術

2023-09-22 12:21:33

Python深拷貝淺拷貝

2024-04-17 09:01:08

Python深拷貝淺拷貝

2022-07-26 08:07:03

Python淺拷貝深拷貝

2023-05-17 08:42:46

深拷貝Golang

2021-01-08 06:15:09

深拷貝淺拷貝寫時拷貝

2009-05-19 17:28:44

深拷貝淺拷貝clone()

2024-03-15 15:03:23

2020-10-12 08:35:22

JavaScript

2021-09-27 11:07:11

深拷貝淺拷貝內(nèi)存

2017-08-16 13:30:05

Java深拷貝淺拷貝

2021-07-16 12:33:24

Javascript深拷貝淺拷貝

2021-09-10 07:41:06

Python拷貝Python基礎

2022-11-07 11:37:27

深拷貝淺拷貝底層

2023-01-05 18:14:32

淺拷貝深拷貝Python

2020-08-03 08:24:26

原型模式拷貝

2024-02-05 22:56:16

C++拷貝開發(fā)

2025-04-27 09:45:58

JavaScript深拷貝淺拷貝

2018-09-26 14:37:17

JavaScript前端編程語言

2021-10-18 09:01:01

前端賦值淺拷貝
點贊
收藏

51CTO技術棧公眾號