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
>>> a=['haha',1,['heihei','xixi']]
>>> id(a)
1454383085576
>>> [id(x) for x in a]
[1454383113304, 1769341424, 1454383128392]
>>> b=a
>>> id(b)
1454383085576
>>> [id(x) for x in b]
[1454383113304, 1769341424, 1454383128392]
>>> a[0]='enen'
>>> a[2].append('lala')
>>> a
['enen', 1, ['heihei','xixi','lala']]
>>> b
['enen', 1, ['heihei','xixi','lala']]
>>> id(a)
1454383085576
>>> id(b)
1454383085576
>>> [id(x) for x in a]
[1454383154600, 1769341424, 1454383155272]
>>> [id(x) for x in b]
[1454383154600, 1769341424, 1454383155272]

a 赋值给 b 就是将变量 b 的引用指向 a, 因此变量 ab 的内存地址相同,a,b 中元素的地址也相同。所以改变a[0],a[2]中的值, 变量 b 也对应着改变。a[0] 的字符串是不可变类型,改变其值需要新开辟一段内存空间。

浅拷贝

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
>>> a=['haha',1,['heihei','xixi']]
>>> b=a.copy()
>>> a
['haha', 1, ['heihei', 'xixi']]
>>> b
['haha', 1, ['heihei', 'xixi']]
>>> id(a)
1454383081288
>>> id(b)
1454383148744
>>> [id(i) for i in a]
[1454383113024, 1769341424, 1454383080648]
>>> [id(i) for i in b]
[1454383113024, 1769341424, 1454383080648]
>>> a[0]='enen'
>>> a[2].append('lala')
>>> a
['enen', 1, ['heihei', 'xixi', 'lala']]
>>> b
['haha', 1, ['heihei', 'xixi', 'lala']]
>>> id(a)
1454383081288
>>> id(b)
1454383148744
>>> [id(i) for i in a]
[1454383113696, 1769341424, 1454383080648]
>>> [id(i) for i in b]
[1454383113024, 1769341424, 1454383080648]

浅拷贝会创建一个新的对象,然后把生成的新对象赋值给新变量。但是对象里的元素依然按引用传递,由第 12 和 13行 可以看出,元素的内存地址并没有改变,只是变量 ab 的内存地址不同。也就是说浅拷贝为对象开辟了新的内存空间,但是对象里的元素依然按照引用传递。由于 a[0] 里的元素字符串是不可变类型,改变其值需要另开辟一段内存空间,而 b[0] 的引用依然指向原地址,所以改变 a[0] 的值并不影响b[0] 的值。a[2] 是可变类型,可以原地改变值,不需要另外开辟新的内存空间,所以 b[2] 的值随着 a[2] 的改变而改变,而内存地址并不需要改变。

切片[:] 操作,list/dir/set 以及 copy() 函数都属于浅拷贝。

深拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
>>> import copy
>>> a=['haha',1,['heihei','xiix']]
>>> b=copy.deepcopy(a)
>>> b
['haha', 1, ['heihei', 'xiix']]
>>> id(a)
1454383167816
>>> id(b)
1454383186568
>>> [id(i) for i in a]
[1454383113024, 1769341424, 1454383169032]
>>> [id(i) for i in b]
[1454383113024, 1769341424, 1454383186888]
>>> a[0]='enen'
>>> a[2].append('lala')
>>> a
['enen', 1, ['heihei', 'xiix', 'lala']]
>>> b
['haha', 1, ['heihei', 'xiix']]
>>> [id(i) for i in a]
[1454383153872, 1769341424, 1454383169032]
>>> [id(i) for i in b]
[1454383113024, 1769341424, 1454383186888]

深拷贝与浅拷贝类似,也是创建一个新的对象,然后把生成的新对象赋值给新变量。但是对原对象元素的操作不同,由第11和13行可以看出,对于不可变元素,深拷贝依然使用引用方式,可变类型元素则新开辟一段内存空间,因为改变不可变类型的元素,需要开辟新的内存空间,而深拷贝的引用还是指向原地址,所以原对象改变其值并不影响深拷贝的值。对于可变类型的元素,深拷贝后元素的内存地址与原对象值的内存地址并不相同,因此两个值的改变与否没有任何关系。

总结

  • Python 中对象的赋值都是进行对象引用(内存地址)传递
  • 对于赋值,两个对象内的元素相互影响,本质上是同一个内存空间,只是在有两个不同的标签而已
  • 对于浅拷贝,它复制了对象,但对于对象中的元素,依然使用原始引用。改变原对象不可改变类型的元素,不会影响拷贝后对象相应的值。改变原对象可变类型元素的值,浅拷贝后对象的值相应也会改变
  • 对于深拷贝,可以把两个对象看成独立的对象,元素的改变在彼此之间没有任何影响。值得注意的是,对于不可变类型的元素,深拷贝后对象相应元素的内存地址与原对象的相同,而可变类型元素的则不同。从第11和13行可以看,a[0],b[0] 以及 a[1],b[1] 的地址相同,而 a[2]b[2]则不同

参考资料

赞赏是对作者最大的支持!
0%