Blog

Python 浅拷贝与深拷贝

赋值不会复制对象,浅拷贝只复制一层。本文讲清楚浅拷贝和深拷贝的分界线到底在哪里,以及 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)或者元组套元组,这个问题就不存在了。

这篇文章对你有帮助吗?

点一颗爱心来评分!

平均评分 0 / 5. 投票总数: 0

还没有人投票。来做第一个评分的人吧。