Python - 對象賦值、淺拷貝、深拷貝的區別


前言

  • Python 中不存在值傳遞,一切傳遞的都是對象的引用,也可以認為是傳址
  • 這里會講三個概念:對象賦值、淺拷貝、深拷貝

 

名詞解釋

  • 變量:存儲對象的引用
  • 對象:會被分配一塊內存,存儲實際的數據,比如字符串、數字、列表
  • 引用:變量指向對象,可以理解為指針

  

實際的一種應用場景

  • 有一個變量 a,存儲了一個值
  • 此時想用另一個變量 b 暫時存儲變量 a 的值,以便后續使用
  • 然后繼續修改變量 a 的值,但修改的時候並不想同步更改變量 b 的值
a=1
b=a
a=2

  

對象賦值

 

不可變對象的賦值

a = 1
b = a

print(a, b)

a += 2
print(a, b)

print("a id:", id(a))
print("b id:", id(b))


# 輸出結果
1 1
2 1
a id: 4564097808
b id: 4564097776
  • 修改變量 a 的值,不會同步修改變量 b 的值
  • 因為賦值操作 a += 2 后,變量 a 存儲的對象引用已經改變了
  • 至於具體的原理,可以看看不可變對象、可變對象的詳解 https://www.cnblogs.com/poloyy/p/15073168.html

 

可變對象的賦值

a = [1, 2, 3]
b = a

print(a, b)

a[1] = 22
print(a, b)

print("a id:", id(a))
print("b id:", id(b))


# 輸出結果
[1, 2, 3] [1, 2, 3]
[1, 22, 3] [1, 22, 3]
a id: 4567683136
b id: 4567683136
  • 修改 a 變量的值,會同步修改變量 b 的值,這不符合上面的說的實際應用場景
  • 因為變量 a、b 指向的對象是可變對象,所以它們保存的對象引用都是一樣的

 

拷貝的誕生

  • 那如果要讓可變對象也能滿足上述實際應用場景,要怎么做呢?
  • 當然就是拷貝
  • 而拷貝又分為淺拷貝、深拷貝,接下來會具體聊一聊兩種拷貝的區別

 

第一個重點總結

  • 對於不可變對象來說,賦值操作其實就可以滿足上面說的實際應用場景
  • 所以!后面要講的淺拷貝、深拷貝對於不可變對象來說,和賦值操作是一樣的效果!
  • 記住!淺拷貝、深拷貝只針對可變對象,即列表、集合、字典!

 

copy 模塊

Python 提供了 copy 模塊,包含了淺拷貝、深拷貝函數

from copy import copy, deepcopy

# 淺拷貝
copy(x)

# 深拷貝
deepcopy(x)

 

淺拷貝

一句話概括:淺拷貝會創建一個新對象,該新對象存儲原始元素的引用

 

淺拷貝后的值是相同的

  • 將列表賦值給變量 old_list
  • 通過 copy() 方法對 old_list 變量指向的對象進行淺拷貝,並賦值給新變量 new_list
  • 因為是對象進行拷貝,所以 new_list 和 old_list 存儲的值是相同的
import copy

old_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
new_list = copy.copy(old_list)

print("Old list:", old_list)
print("New list:", new_list)


# 輸出結果
Old list: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
New list: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

 

淺拷貝后的會產生一個新的對象

  • 雖然 old_list 和 new_list 存儲的值是相同的,但淺拷貝的操作是產生了一個新的對象
  • 所以 old_list 和 new_list 指向的對象並不是同一個
import copy

old_list = [[1, 2], [3, 4]]
new_list = copy.copy(old_list)

old_list.append([5, 6])

print("Old list:", old_list, "id is :", id(old_list))
print("New list:", new_list, "id is :", id(new_list))


# 輸出結果
Old list: [[1, 2], [3, 4], [5, 6]] id is : 4366240704
New list: [[1, 2], [3, 4]] id is : 4366246720

可以看到內存地址是不同的,所以給 old_list 新增一個元素並不會同步讓 new_list 也新增

 

原理圖

  • 淺拷貝生成了一個新對象,然后賦值給 new_list
  • new_list、old_list 指向的列表對象不是同一個,但值相同
  • 重點:對於列表對象中的元素,淺拷貝產生的新對象只存儲原始元素的引用(內存地址),所以兩個列表對象的元素的引用都指向同一個內存地址

 

那為什么要深拷貝呢?

修改列表內的不可變對象元素

上面的栗子是直接添加元素,來看看修改元素會怎么樣

# 不可變元素
import copy

old_list = [1, 2, "string", (1, 2,)]
new_list = copy.copy(old_list)

old_list[1] += 22
old_list[2] += "s"
old_list[3] += (3,)

print("Old list:", old_list)
print("New list:", new_list)


# 輸出結果
Old list: [1, 24, 'strings', (1, 2, 3)]
New list: [1, 2, 'string', (1, 2)]

修改 old_list 的三種不可變對象元素,均不會同步給 new_list

 

修改不可變對象的原理圖

 

修改列表內的可變對象元素

# 可變元素
import copy

old_list = [[1, 2], [3, 4]]
new_list = copy.copy(old_list)

old_list[0][0] += 99
old_list[1][0] += 97

print("Old list:", old_list, "old list id:", id(old_list), " old list[0] id:", id(old_list[0]))
print("new list:", new_list, "new list id:", id(new_list), " new list[0] id:", id(new_list[0]))


# 輸出結果
Old list: [[100, 2], [100, 4]] old list id: 4430308096  old list[0] id: 4430302400
new list: [[100, 2], [100, 4]] new list id: 4430308416  new list[0] id: 4430302400

從輸出結果看到

  • 兩個變量保存了不同的對象引用
  • 但是可變對象元素的內存地址仍然是同一個

 

修改可變對象的原理圖

 

總結

  • 修改可變對象是在原始對象上直接操作的
  • 淺拷貝產生的新對象存儲的仍然是原始對象的內存地址
  • 所以修改可變對象的時候,新對象的值也會被同步修改,因為新舊列表對象的元素的引用是指向同一個內存地址
  • 當修改可變對象的時候,不滿足一開始說的實際應用場景,所以誕生了深拷貝

 

深拷貝

  • 創建一個新對象,且存儲的對象引用也是新的
  • 深,意味着會把所有子元素對象也復制生成一個新對象

 

栗子一

# 深拷貝
old_list = [[1, 2], [3, 4]]
new_list = copy.deepcopy(old_list)

old_list[0][0] += 99
old_list[1][0] += 97

print("Old list:", old_list, "old list id:", id(old_list), " old list[0] id:", id(old_list[0]))
print("new list:", new_list, "new list id:", id(new_list), " new list[0] id:", id(new_list[0]))


# 輸出結果
Old list: [[100, 2], [100, 4]] old list id: 4430308480  old list[0] id: 4430211392
new list: [[1, 2], [3, 4]] new list id: 4430308096  new list[0] id: 4430308864

從輸出結果看到

  • 兩個變量保存了不同的對象引用
  • 可變對象元素(子對象)的內存地址也是不同的

 

栗子二

假設是一個三維列表呢

# 深拷貝-三維數組
old_list = [[1, [10, 9]], [3, 4]]
new_list = copy.deepcopy(old_list)

old_list[0][1][0] += 90

print("Old list:", old_list)
print("New list:", new_list)


# 輸出結果
Old list: [[1, [100, 9]], [3, 4]]
New list: [[1, [10, 9]], [3, 4]]

兩個變量依舊是獨立的

 

深拷貝原理圖

 

淺拷貝的多種實現方式

https://www.cnblogs.com/poloyy/p/15086511.html

 

面試題:淺拷貝、深拷貝的區別

  1. 淺拷貝和深拷貝只有在可變對象才會生效,不可變對象的賦值操作、淺拷貝、深拷貝的效果是一樣的
  2. 淺拷貝會將對象復制生成一個新對象,但新對象仍然存儲原始對象的引用,當原始對象是可變對象,然后修改它的值時,新舊對象會同時改變
  3. 深拷貝不僅會將對象復制生成一個新對象,且所有原始對象都會復制生成新對象,即使原始對象是可變對象,新對象存儲的對象引用也是新的,所以改變舊對象的可變對象時,不會影響新對象


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM