Python 可变对象的赋值
在使用 Python 列表时, 出现了修改其中一个列表, 其他列表联动改变这个情况, 在查阅文档后, 写在这里记录一下.
出现这个情况的原因是因为我使用了 a = b
这个赋值语句, 我下意识的认为 a, b 是两个不同的对象, 其实在 Python 一切都是对象, 所谓的变量, 只是指向一个对象的内存地址. 我们平时使用的赋值语句 =
其实只是让一个一个变量指向另一个变量指向的内存地址. 例如:
a = 1
b = a
print(a, b)
print(id(a))
print(id(b))
a = 2
print(a, b)
1 1
140712422177856
140712422177856
2 1
可以看到两个变量指向同一个内存地址, 这是 Python 解释器为了节省内存资源, 选择的一种方式. 可以看到, 当变量指向不可变类型时候, b 的值并不会随着 a 的变化而变化.
然而这种方式有一个弊端, 当我们面对可变类型时, 就会发生一些不可控的事情.
a = [1, 2, 3]
b = a
print(a, b)
print(id(a))
print(id(b))
a[0] = 4
print(a, b)
[1, 2, 3] [1, 2, 3]
2537876904712
2537876904712
[4, 2, 3] [4, 2, 3]
可以看到, 因为 a, b 两个变量指向的是同一个内存地址, 当我们修改 a 的数据时, b会一起改变. 常规情况下,这并不是我们想要的结果.
那么, 有没有办法把数据拷贝到一块新的内存地址中呢?
浅拷贝与深拷贝
这就需要用到 Python 内置库 copy. 我们需要用到这个库中的 copy, deepcopy 两个方法.
浅拷贝
我们使用 b = copy.copy(a)
进行浅拷贝, 执行这个语句后 a, b 是两个不同的对象, 指向不同的内存地址.
但是 a, b 中的子对象还是会指向同一地址, 例如下文中的 a 列表中的字典, b 列表中的字典就是子对象, 他们其实指向的是同一内存地址.
import copy
a = [1, 2, 3, {"a": 1, "b": 2, "c": 3}]
b = copy.copy(a)
print(a)
print(b)
print(id(b))
print(id(a))
a[0] = 2
print(a)
print(b)
[1, 2, 3, {"a": 1, "b": 2, "c": 3}]
[1, 2, 3, {"a": 1, "b": 2, "c": 3}]
2537876907720
2537876907400
[2, 2, 3, {"a": 1, "b": 2, "c": 3}]
[1, 2, 3, {"a": 1, "b": 2, "c": 3}]
可以看到, a, b 指向了不同的地址. 我们看下 a, b 中的子对象指向的内存地址
import copy
a = [1, 2, 3, {"a": 1, "b": 2, "c": 3}]
b = copy.copy(a)
print(id(a[3]))
print(id(b[3]))
2537876900904
2537876900904
可以看到, a, b 的子对象指向的内存. 因此, 当我们对这个字典进行操作时, 另一个数组对象中的也会随之变化, 原理同上.
那么, 有没有什么可以规避这一现象呢? 接下来我们走进深拷贝.
深拷贝
我们使用 b = copy.deepcopy(a)
进行深拷贝, 执行这个语句后 a, b 是两个不同的对象, 指向不同的内存地址. 且 a, b 的子对象也指向不同的内存地址, a, b 指向除了值相等, 完全没有任何关系的对象.
import copy
a = [1, 2, 3, {"a": 1, "b": 2, "c": 3, "f": [1, 2, 3]}]
b = copy.deepcopy(a)
print(a)
print(b)
print(id(b))
print(id(a))
print(id(a[3]))
print(id(b[3]))
print(id(a[3]["f"]))
print(id(b[3]["f"]))
a[1] = 0
a[3]["d"] = 4
print(a)
print(b)
[1, 2, 3, {"a": 1, "b": 2, "c": 3, "f": [1, 2, 3]}]
[1, 2, 3, {"a": 1, "b": 2, "c": 3, "f": [1, 2, 3]}]
2537876918920
2537876918728
2537876851592
2537876851672
2537876916296
2537876919304
[1, 0, 3, {"a": 1, "b": 2, "c": 3, "f": [1, 2, 3], "d": 4}]
[1, 2, 3, {"a": 1, "b": 2, "c": 3, "f": [1, 2, 3]}]
可以看到, 不论对象嵌套有多少个子对象, 进行深拷贝后得到的对象及其子对象, 都指向不同的地址. 同样的, 对其中任何数据的修改都不会影响到另一个对象.
可以看到, Python 为了适应不同的需求, 为我们提供了非常灵活的方法, 可以根据需求灵活使用.