赋值、深拷贝、浅拷贝
一、赋值
在Python中,赋值只是创建一个变量,该变量指向原来的内存地址,换句话说就是类似c语言内的指针,用一个指针指向一个元素的内存地址。
In [1]: a = [11, 22]
In [2]: b = a
In [3]: id(a)
Out[3]: 63796288
In [4]: id(b)
Out[4]: 63796288
上面的代码中,我们创建了一个列表a,并且把a赋值给b,此时查看a的id为63796288,而去查看b的id此时同样为63796288。说明在python中,如果使用=
来赋值的话,只是将变量指向该元素的内存地址,如果元素发生改变,此时b也会跟着改变。
In [5]: a.append(33)
In [6]: a
Out[6]: [11, 22, 33]
In [7]: b
Out[7]: [11, 22, 33]
二、浅拷贝
浅拷贝是对于一个对象的顶层拷贝。通俗的理解是:拷贝了引用,并没有拷贝内容。
In [8]: a = [11, 22]
In [9]: b = [33, 44]
In [10]: c = [a, b]
In [11]: id(a)
Out[11]: 75386152
In [12]: id(b)
Out[12]: 75370248
In [13]: id(c)
Out[13]: 75371024
In [14]: id(c[0])
Out[14]: 75386152
In [15]: id(c[1])
Out[15]: 75370248
In [16]: import copy
In [17]: d = copy.copy(c)
In [18]: d
Out[18]: [[11, 22], [33, 44]]
In [19]: id(d)
Out[19]: 75468472
In [20]: id(d[0])
Out[20]: 75386152
In [21]: id(d[1])
Out[21]: 75370248
上面的代码可以看出,浅拷贝只是将c的最外层放到了一个新的地址,但是里面的内容还是指向c所指向的东西,并没有自己重新创建。如果此时改变a的值,d也会跟着改变。
In [22]: a.append(33)
In [23]: c
Out[23]: [[11, 22, 33], [33, 44]]
In [24]: d
Out[24]: [[11, 22, 33], [33, 44]]
三、深拷贝
深拷贝是对于一个对象所有层次的拷贝(递归)
In [25]: e = copy.deepcopy(c)
In [26]: e
Out[26]: [[11, 22, 33], [33, 44]]
In [27]: id(e)
Out[27]: 65404976
In [28]: id(e[0])
Out[28]: 75337400
In [29]: id(e[1])
Out[29]: 65407896
从上面也可以看出,e中的所有元素都已经在一个新的地址中,此时如论怎么改变a的值,e都不会跟着改变。
In [30]: a.append(44)
In [31]: a
Out[31]: [11, 22, 33, 44]
In [32]: c
Out[32]: [[11, 22, 33, 44], [33, 44]]
In [33]: d
Out[33]: [[11, 22, 33, 44], [33, 44]]
In [34]: e
Out[34]: [[11, 22, 33], [33, 44]]
四、拷贝的其他方式
4.1 分片表达式可以赋值一个序列
In [1]: a = [11, 22]
In [2]: b = [33, 44]
In [3]: c = [a, b]
In [4]: d = c[:]
In [5]: id(c)
Out[5]: 73034568
In [6]: id(d)
Out[6]: 78822136
In [7]: id(c[0])
Out[7]: 72958200
In [8]: id(d[0])
Out[8]: 72958200
In [9]: a.append(3)
In [10]: c
Out[10]: [[11, 22, 3], [33, 44]]
In [11]: d
Out[11]: [[11, 22, 3], [33, 44]]
4.2 字典的copy方法
In [18]: d = dict(name="summer", age=20)
In [19]: co = d.copy()
In [20]: id(d)
Out[20]: 75186896
In [21]: id(co)
Out[21]: 75250928
In [22]: d
Out[22]: {'name': 'summer', 'age': 20}
In [23]: co
Out[23]: {'name': 'summer', 'age': 20}
从上面看到,字典的copy与=
不一样,并不是指向地址,那它是深拷贝还是浅拷贝呢?
In [29]: d = dict(name="summer", age=20, children_age=[11, 22])
In [30]: co = d.copy()
In [31]: id(d)
Out[31]: 73155856
In [32]: id(co)
Out[32]: 72976640
In [33]: d["children_age"].append(33)
In [34]: d
Out[34]: {'name': 'summer', 'age': 20, 'children_age': [11, 22, 33]}
In [35]: co
Out[35]: {'name': 'summer', 'age': 20, 'children_age': [11, 22, 33]}
因此,我们也可以知道,字典的copy方法也是用的浅拷贝
五、注意点
5.1 浅拷贝对不可变类型和可变类型的copy不同
- copy.copy对于可变类型,会进行浅拷贝
- copy.copy对于不可变类型,不会拷贝,仅仅是指向
In [88]: a = [11,22,33]
In [89]: b = copy.copy(a)
In [90]: id(a)
Out[90]: 59275144
In [91]: id(b)
Out[91]: 59525600
In [92]: a.append(44)
In [93]: a
Out[93]: [11, 22, 33, 44]
In [94]: b
Out[94]: [11, 22, 33]
In [95]: a = (11,22,33)
In [96]: b = copy.copy(a)
In [97]: id(a)
Out[97]: 58890680
In [98]: id(b)
Out[98]: 58890680
如果一个数元组,那么浅拷贝只是执行了应用(赋值),但是深拷贝还是会进行深拷贝,即递归所有。
# -*- coding: utf-8 -*-
import copy
a = [11, 22]
b = (a, )
c = [b]
d = copy.copy(c)
e = copy.deepcopy(c)
print(id(a))
print(id(b))
print(id(c))
print(id(c[0]))
print(id(d))
print(id(d[0]))
print(id(e))
print(id(e[0]))
有兴趣,也可以执行以下下面的代码,会发现:
12666320
12818128
12667480
12818128
137572640
12818128
13024832
13019824