前言
面試的時候經常會問到深拷貝和淺拷貝,那么python的深拷貝和淺拷貝有什么區別呢?
思考題
先來看 2 個簡單的案例, 對元素 a/aa 重新賦值一個新的變量 b/bb 后,改變原來 a/aa 的值,看會不會影響新的變量 b/bb 的值
# 1.str
a = "hello"
b = a
a = "world"
print('a: {}'.format(a))
print('b: {}'.format(b))
# 2.list
aa = [1, 2, 3]
bb = aa
aa.append(4)
print('aa: {}'.format(aa))
print('bb: {}'.format(bb))
運行結果
a: world
b: hello
aa: [1, 2, 3, 4]
bb: [1, 2, 3, 4]
這是個很有趣的事情,字符串重新賦值給b后,改變原來a的值,b不會跟着變。
但是list重新賦值給bb后,改變aa的值,bb的值也跟着變了。
這里有個知識點:在python中,都是將“對象的引用(內存地址)”賦值給變量的。其次,在python中有6個標准數據類型,他們分為可變和不可變兩類。
可變和不可變對象
在python中有6個標准數據類型,他們分為可變和不可變兩類。
- 不可變類型:Number(數字)String(字符串)Tuple(元組)
- 可變類型:List(列表)Dictionary(字典)Set(集合)
可變對象和不可變對象的內存地址可以通過id函數獲取
- 可變對象:可變對象可以在其 id() 保持固定的情況下改變其取值;
- 不可變對象:具有固定值的對象。不可變對象包括數字、字符串和元組。這樣的對象不能被改變。如果必須存儲一個不同的值,則必須創建新的對象。
- id(object): 函數用於獲取對象的內存地址,函數返回對象的唯一標識符,標識符是一個整數。
字符串和數字都是不可變類型,不同變量賦值一樣,通過id獲取的內存地址是一樣的
# 作者-上海悠悠 QQ交流群:717225969
# blog地址 https://www.cnblogs.com/yoyoketang/
a = "abc"
b = "abc"
print(id(a))
print(id(b))
print(a is b)
c = 100
d = 100
print(id(c))
print(id(d))
print(c is d)
運行結果
1557212603592
1557212603592
True
1561032832
1561032832
True
list、dict 和 set集合是可變類型,雖然值一樣,但是id獲取的內存地址不一樣
# 作者-上海悠悠 QQ交流群:717225969
# blog地址 https://www.cnblogs.com/yoyoketang/
a = {"key": "123"}
b = {"key": "123"}
print(id(a))
print(id(b))
print(a is b)
print(a == b)
c = [1, 2, 3]
d = [1, 2, 3]
print(id(c))
print(id(d))
print(c is d)
print(c == d)
運行結果
1638920310144
1638920310216
False
True
1638921292360
1638921292680
False
True
現在知道了id函數獲取內存地址,我們說的深拷貝和淺拷貝是針對可變對象:list、dict 和 set集合
copy模塊
python 中的深拷貝和淺拷貝使用 copy 模塊
淺拷貝 A shallow copy constructs a new compound object and then (to the extent possible) inserts references into it to the objects found in the original.
上面這段話是官方文檔上的描述,有2個含義:
- 1.淺拷貝會創建一個新的容器對象(compound object)
- 2.對於對象中的元素,淺拷貝就只會使用原始元素的引用(內存地址)
常見的淺拷貝操作有:
- 使用切片操作[:]
- 使用工廠函數(如list/dict/set)
- copy模塊的copy()方法
深拷貝 A deep copy constructs a new compound object and then, recursively, inserts copies into it of the objects found in the original.
上面這段話是官方文檔上的描述,也是有2個含義:
- 1.深拷貝和淺拷貝一樣,都會創建一個新的容器對象(compound object)
- 2.和淺拷貝的不同點在於,深拷貝對於對象中的元素,深拷貝都會重新生成一個新的對象
淺拷貝
淺拷貝使用 copy 模塊的 copy 方法
# 作者-上海悠悠 QQ交流群:717225969
# blog地址 https://www.cnblogs.com/yoyoketang/
import copy
a = [1, "hello", [2, 3], {"key": "123"}]
b = copy.copy(a)
print(id(a)) # 外面容器拷貝了,所以a和b的id不一樣
print(id(b))
# a和b容器里面的元素對象id
print(id(a[2]))
print(id(b[2]))
運行結果
1340977220424
1340977221576
1340977220168
1340977220168
淺拷貝是拷貝了list外面一層的, 創建一個新的容器對象(compound object),所以a和b的id是不一樣的
對於容器里面的元素對象,淺拷貝就只會使用原始元素的引用(內存地址),所以可以看到子元素的內存地址還是一樣的
如果改變a里面的不可變對象數字和字符串,此時a和b的值就不一樣了,但是b的后面沒改變的元素還是指向a
# 改變a的 數字和字符串對象
a[0] = 2
# a 和b 的值不一樣了
print(a)
print(b)
# 但是后面的元素還是指的a
print(id(a[2]))
print(id(b[2]))
運行結果
[2, 'hello', [2, 3], {'key': '123'}]
[1, 'hello', [2, 3], {'key': '123'}]
2488134044232
2488134044232
如果改變a里面的可變對象, 把[2, 3]里面的3改成 [2, 4]
# 改變a的 可變對象 [2, 4]
a[2][1] = 4
print(a)
print(b)
print(id(a[2]))
print(id(b[2]))
運行結果
[1, 'hello', [2, 4], {'key': '123'}]
[1, 'hello', [2, 4], {'key': '123'}]
2385125673544
2385125673544
此時b會隨着a的改變而改變,這就是淺拷貝了
深拷貝
淺拷貝使用 copy 模塊的 deepcopy 方法
import copy
# 作者-上海悠悠 QQ交流群:717225969
# blog地址 https://www.cnblogs.com/yoyoketang/
a = [1, "hello", [2, 3], {"key": "123"}]
b = copy.deepcopy(a)
print(id(a)) # 外面容器拷貝了,所以a和b的id不一樣
print(id(b))
# a和b容器里面的元素對象id
print(id(a[2]))
print(id(b[2]))
# 改變a的 可變對象 [2, 4]
a[2][1] = 4
print(a)
print(b)
print(id(a[2]))
print(id(b[2]))
深拷貝和淺拷貝的不同點在於,深拷貝對於對象中的元素,深拷貝都會重新生成一個新的對象。
所以不管a怎么變,都不會影響b的值
賦值
賦值跟淺拷貝 深拷貝是有區別的,可以看下面的示例
# 作者-上海悠悠 QQ交流群:717225969
# blog地址 https://www.cnblogs.com/yoyoketang/
a = [1, "hello", [2, 3], {"key": "123"}]
b = a
print(id(a))
print(id(b))
# a和b容器里面的元素對象id
print(id(a[2]))
print(id(b[2]))
a[0] = 2
print(a)
print(b)
運行結果
1992198687560
1992198687560
1992198687304
1992198687304
[2, 'hello', [2, 3], {'key': '123'}]
[2, 'hello', [2, 3], {'key': '123'}]
賦值語句並沒有生成新的容器,跟淺拷貝的區別在於外面的容器也是指向的a的內存地址,並沒有生成新的容器
參考博客資料https://www.nowcoder.com/discuss/203654?type=2&order=0&pos=1232&page=0
參考博客資料https://copyfuture.com/blogs-details/2020031720252559878eggumgw4iaj7c