赋值不会复制对象,浅拷贝只复制一层。本文讲清楚浅拷贝和深拷贝的分界线到底在哪里,以及 copy.deepcopy 是怎么处理的。
把列表赋给另一个名字,什么也没有复制。两个名字指向同一个对象,通过其中一个做的修改,另一个也能看到。
复制分两层:浅拷贝新建一个外层对象,里面装的还是原来那些内层对象;深拷贝则把整棵结构重建一遍。
赋值不是复制
一个列表,两个名字。
# assignment does not copy
original = [1, 2, 3]
alias = original
alias.append(4)
print(original, alias is original)
输出:
[1, 2, 3, 4] True
浅拷贝
对列表来说,.copy()、list(x) 和 x[:] 效果完全一样。
# a shallow copy is a new outer object
shallow = original.copy() # list(original) and original[:] do the same
shallow.append(5)
print(original, shallow, shallow is original)
输出:
[1, 2, 3, 4] [1, 2, 3, 4, 5] False
现在外层列表已经分开了。
浅拷贝到哪里为止
新列表里装的,还是旧列表里那几个内层列表。
# shallow means one level
rows = [[1, 2], [3, 4]]
copied = rows.copy()
copied[0].append(99)
print(rows, copied)
print('inner list shared:', rows[0] is copied[0])
输出:
[[1, 2, 99], [3, 4]] [[1, 2, 99], [3, 4]]
inner list shared: True
两个外层列表都显示了这次修改,因为内层列表只有一个。
深拷贝
copy.deepcopy 会遍历整个结构,把遇到的每个对象都复制一份。
# deepcopy copies the whole tree
import copy
rows2 = [[1, 2], [3, 4]]
deep = copy.deepcopy(rows2)
deep[0].append(99)
print(rows2, deep)
print('inner list shared:', rows2[0] is deep[0])
输出:
[[1, 2], [3, 4]] [[1, 2, 99], [3, 4]]
inner list shared: False
修改只影响副本。
字典和嵌套配置
实际工作中最常见的情况是:一个配置字典,被别处的代码改了。
# it works on dicts and objects too
config = {'db': {'host': 'localhost', 'ports': [5432]}}
shallow_cfg = copy.copy(config)
deep_cfg = copy.deepcopy(config)
config['db']['ports'].append(5433)
print('shallow sees the change:', shallow_cfg['db']['ports'])
print('deep does not:', deep_cfg['db']['ports'])
输出:
shallow sees the change: [5432, 5433]
deep does not: [5432]
循环引用也能处理
deepcopy 会用一个备忘字典记下已经复制过的对象,所以指向自身的结构不会导致无限递归。
# cycles are handled
node = {'name': 'root'}
node['self'] = node
clone = copy.deepcopy(node)
print(clone['self'] is clone, clone is node)
输出:
True False
副本指向的是副本自己。
元组护不住里面的东西
元组不能重新赋值,但里面的列表照样能改,而且浅拷贝会共享这些列表。
# tuples of mutables are still shared by a shallow copy
data = ([1, 2], [3, 4])
shallow_t = copy.copy(data)
shallow_t[0].append(3)
print(data, shallow_t[0] is data[0])
输出:
([1, 2, 3], [3, 4]) True
不可变容器里可以装可变的内容。拿元组当默认值时,记住这一点。
要点
b = a只是多了一个名字,不是多了一个对象。- 浅拷贝只复制外层容器。
copy.deepcopy复制整棵结构,也能处理循环引用。- 深拷贝很慢。需要时再用,不要默认就用。
如果你经常要深拷贝,通常说明这些数据本该是不可变的。换成冻结的数据类(dataclass)或者元组套元组,这个问题就不存在了。