Python的赋值与深浅拷贝实例解析

对象是一个定值

此时三种方法的作用实际上是相同的。

1
2
3
4
5
6
7
8
9
10
11
import copy

a = "亚丝娜"
b = a
c = copy.copy(a)
d = copy.deepcopy(a)

print("源:id(a)->>>", id(a))
print("赋值:id(b)->>>", id(b))
print("浅拷贝:id(c)->>>", id(c))
print("深拷贝:id(d)->>>", id(d))

输出如下:

1
2
3
4
源:id(a)->>> 4394180400
赋值:id(b)->>> 4394180400
浅拷贝:id(c)->>> 4394180400
深拷贝:id(d)->>> 4394180400

如果a的值被更改,只有a本身的id会改变,bcd都不变。

对象是一个引用

  • 首先,元组,数组,字典,类等的本质都是引用,或称之为“指针”,每个引用指向的实体都是有其相应地址的,比如这里的“亚丝娜”在内存中有一个具体的地址,而[“亚丝娜”]则是一个对于内存中“亚丝娜”实体的一个引用集,这个引用集本身也有一个独特的地址。
  • 对于不可变对象,Python 用引用计数的方式管理它们,所以 Python 不会对值相同的不可变对象,申请单独的内存空间。只会记录它的引用次数。
  • 使用“引用集”赋值是把引用集本身的地址赋给了左边的变量,即一个引用的引用。
  • 使用赋值的方法得到的对象,当原“引用集”中的任何引用发生任何改变时,其都会随着改变,就像一个快捷方式。但如果原“引用集”直接被覆盖了,则不会随之改变。

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# 一位数组更改内层元素
a=["亚丝娜"]
b=a
b = a
c = copy.copy(a)
d = copy.deepcopy(a)

a[0] = "桐人"
print("源:id(a)->>>", id(a))
print("赋值:id(b)->>>", id(b))
print("浅拷贝:id(c)->>>", id(c))
print("深拷贝:id(d)->>>", id(d))
print(a,b,c,d)

"""
源:id(a)->>> 4585988416
赋值:id(b)->>> 4585988416
浅拷贝:id(c)->>> 4585374256
深拷贝:id(d)->>> 4585117648
['桐人'] ['桐人'] ['亚丝娜'] ['亚丝娜']
"""

# 一位数组完全变更
a=["亚丝娜"]
b=a
b = a
c = copy.copy(a)
d = copy.deepcopy(a)

a=["利兹"]
print("源:id(a)->>>", id(a))
print("赋值:id(b)->>>", id(b))
print("浅拷贝:id(c)->>>", id(c))
print("深拷贝:id(d)->>>", id(d))
print(a,b,c,d)

"""
源:id(a)->>> 4586047600
赋值:id(b)->>> 4583430096
浅拷贝:id(c)->>> 4585223344
深拷贝:id(d)->>> 4585988416
['利兹'] ['亚丝娜'] ['亚丝娜'] ['亚丝娜']
"""

# 二维数组更改最内层元素
a=[["亚丝娜"]]
b=a
b = a
c = copy.copy(a)
print("源(重赋值前):id(a)->>>", id(a))
d = copy.deepcopy(a)

a[0][0]="利兹"
print("源:id(a)->>>", id(a))
print("赋值:id(b)->>>", id(b))
print("浅拷贝:id(c)->>>", id(c))
print("深拷贝:id(d)->>>", id(d))
print(a,b,c,d)

"""
源(重赋值前):id(a)->>> 4586327008
源:id(a)->>> 4586327008
赋值:id(b)->>> 4586327008
浅拷贝:id(c)->>> 4586041088
深拷贝:id(d)->>> 4585375216
[['利兹']] [['利兹']] [['利兹']] [['亚丝娜']]
"""

# 二维数组更改第一维
a=[["亚丝娜"]]
b=a
b = a
c = copy.copy(a)
print("源(重赋值前):id(a)->>>", id(a))
d = copy.deepcopy(a)

a[0]=["利兹"]
print("源:id(a)->>>", id(a))
print("赋值:id(b)->>>", id(b))
print("浅拷贝:id(c)->>>", id(c))
print("深拷贝:id(d)->>>", id(d))
print(a,b,c,d)

"""
源(重赋值前):id(a)->>> 4585239328
源:id(a)->>> 4585239328
赋值:id(b)->>> 4585239328
浅拷贝:id(c)->>> 4586082944
深拷贝:id(d)->>> 4586327008
[['利兹']] [['利兹']] [['亚丝娜']] [['亚丝娜']]
"""

# 二维数组完全变更
a=[["亚丝娜"]]
b=a
b = a
c = copy.copy(a)
print("源(重赋值前):id(a)->>>", id(a))
d = copy.deepcopy(a)

a=[["利兹"]]
print("源:id(a)->>>", id(a))
print("赋值:id(b)->>>", id(b))
print("浅拷贝:id(c)->>>", id(c))
print("深拷贝:id(d)->>>", id(d))
print(a,b,c,d)

"""
源(重赋值前):id(a)->>> 4586082944
源:id(a)->>> 4585468704
赋值:id(b)->>> 4586082944
浅拷贝:id(c)->>> 4586327008
深拷贝:id(d)->>> 4586041088
[['利兹']] [['亚丝娜']] [['亚丝娜']] [['亚丝娜']]
"""
  • 赋值是完全的快捷方式。
  • 浅拷贝的实质是对一个“引用集”的所有引用的拷贝,即拷贝了一份“引用集”中记录的所有的这些的不可变对象的地址,但只拷贝了一层,或称并没有把这些对象本身拷贝一遍。
  • 如果引用集里还有引用集x,那么浅拷贝对x的作用和赋值相同,即如果x里的元素变了,浅拷贝的结果还是会跟着变。
  • 深拷贝把拷贝对象里面的所有层的引用集全部做了拷贝动作,直到引用到不可变变量,所以可以说深拷贝出来的结果和其拷贝对象没有任何耦合关系了。

简要版本

  • 由于 Python 内部引用计数的特性,对于不可变对象,浅拷贝和深拷贝的作用是一致的,就相当于复制了一份副本,原对象内部的不可变对象的改变,不会影响到复制对象。
  • 浅拷贝的拷贝。其实是拷贝了原始元素的引用(内存地址),所以当拷贝可变对象时,原对象内可变对象的对应元素的改变,会在复制对象的对应元素上,有所体现。
  • 深拷贝在遇到可变对象时,又在内部做了新建了一个副本。所以,不管它内部的元素如何变化,都不会影响到原来副本的可变对象。

参考

5张图彻底理解Python中的浅拷贝与深拷贝