python深拷贝与浅拷贝

以前关于 Python 的做用域、赋值、参数传递,咱们接连谈了几篇文章:编程

今天咱们依然要就相关话题继续下去。app

首先是上次最后的思考题:编辑器

m = [1, 2, [3]]
n = m[:]
n[1] = 4
n[2][0] = 5
print(m)

m 的结果是什么?函数

正确答案是 [1, 2, [5]] ,此次比上次好点,有 35% 的正确率。人工智能

当时我留了个提示,说和浅拷贝、深拷贝有关,如今咱们就来具体说一说。spa

假设有这样一个 list 变量 m,其中有 4 个元素(别被嵌套迷惑了):3d

m = [1, 2, [3, 4], [5, [6, 7]]]

为了更直观的表示,我来画个图:code

如今咱们想要再来“复制”一个一样的变量。也许第一个闪过脑中的念头就是:对象

n = m

但看了前面的文章后你应该知道,这样的 赋值只至关于增长了一个标签,并无新的对象产生blog

id 验证下就知道, m 和 n 仍然是同一个东西 。那么他们内部的元素天然也是同样的,对其中一个进行修改,另外一个也会跟着变:

m = [1, 2, [3, 4], [5, [6, 7]]]
print('m:', id(m))
print([id(i) for i in m])
n = m
print('n:', id(n))
print([id(i) for i in n])
print(n is m)
print(n[0] is m[0])
print(n[2] is m[2])
n[0] = -1
print(m)
n[2][1] = -1
print(m)

输出

m: 4564554888
[4556507504, 4556507536, 4564554760, 4564555016]
n: 4564554888
[4556507504, 4556507536, 4564554760, 4564555016]
True
True
True
[-1, 2, [3, 4], [5, [6, 7]]]
[-1, 2, [3, -1], [5, [6, 7]]]

所以有人将此操做称为“ 旧瓶装旧酒 ”,只是多贴了一层标签,这不能达到咱们的目的。要获得一个对象的“拷贝”,咱们须要用到 copy 方法:

from copy import copy
m = [1, 2, [3, 4], [5, [6, 7]]]
print('m:', id(m))
print([id(i) for i in m])
n = copy(m)
print('n:', id(n))
print([id(i) for i in n])
print(n is m)
print(n[0] is m[0])
print(n[2] is m[2])
n[0] = -1
print(m)
n[2][1] = -1
print(m)

输出

m: 4340253832
[4333009264, 4333009296, 4340253704, 4340253960]
n: 4340268104
[4333009264, 4333009296, 4340253704, 4340253960]
False
True
True
[1, 2, [3, 4], [5, [6, 7]]]
[1, 2, [3, -1], [5, [6, 7]]]

从结果中能够看出, n 和 m 已不是同一个对象 ,对于某个元素的从新赋值不会影响原对象。可是,它们 内部的元素全都是同样的 ,因此对一个可变类型元素的修改,则仍然会反应在原对象中。

(其实这里一、2也是指向同一个对象,但做为不可变对象来讲,它们互不影响,直观上的感觉就至关因而复制了一份,故简化如图上所示)

这种复制方法叫作 浅拷贝shallow copy ),又被人形象地称做“ 新瓶装旧酒 ”,虽然产生了新对象,但里面的内容仍是来自同一份。

若是要完全地产生一个和原对象彻底独立的复制品,得使用 深拷贝deep copy ):

from copy import deepcopy
m = [1, 2, [3, 4], [5, [6, 7]]]
print('m:', id(m))
print([id(i) for i in m])
n = deepcopy(m)
print('n:', id(n))
print([id(i) for i in n])
print(n is m)
print(n[0] is m[0])
print(n[2] is m[2])
n[0] = -1
print(m)
n[2][1] = -1
print(m)

输出

m: 4389131400
[4381886832, 4381886864, 4389131272, 4389131528]
n: 4389131208
[4381886832, 4381886864, 4389131656, 4389145736]
False
True
False
[1, 2, [3, 4], [5, [6, 7]]]
[1, 2, [3, 4], [5, [6, 7]]]

此时, 对新对象中元素作任何改动都不会影响原对象 。新对象中的子列表,不管有多少层,都是新的对象,有不一样的地址。

按照前面的比喻,深拷贝就是“ 新瓶装新酒 ”。

你可能会注意到一个细节:n 中的前两个元素的地址仍然和 m 中同样。这是因为它们是 不可变对象,不存在被修改的可能,因此拷贝和赋值是同样的

因而,深拷贝也能够理解为,不只是对象自身的拷贝,并且对于对象中的每个子元素,也都进行一样的拷贝操做。这是一种 递归 的思想。

不过额外要说提醒一下的是, 深拷贝的实现过程并非彻底的递归 ,不然若是对象的某级子元素是它自身的话,这个过程就死循环了。实际上, 若是遇到已经处理过的对象,就会直接使用其引用,而再也不重复处理 。听上去有点难懂是否是?想一想这个例子大概就会理解了:

from copy import deepcopy
m = [1, 2]
m.append(m)
print(m, id(m), id(m[2]))
n = deepcopy(m)
print(n, id(n), id(n[2]))

输出

[1, 2, [...]] 4479589576 4479589576
[1, 2, [...]] 4479575048 4479575048

最后,仍是给各位留个思考:

from copy import deepcopy
a = [3, 4]
m = [1, 2, a, [5, a]]
n = deepcopy(m)
n[3][1][0] = -1
print(n)

深拷贝后的 n,修改了其中一个元素值,会是怎样的效果?

思考一下输出会是什么?

而后本身在电脑上或者咱们的在线编辑器 Crossin的编程教室 - 在线Python编辑器 里输入代码运行下看看结果,再想一想为何。

欢迎留言给出你的解释。

════

其余文章及回答:

如何自学Python | 新手引导 | 精选Python问答 | Python单词表 | 人工智能 | 嘻哈 | 爬虫 | 我用Python | 高考 | requests | AI平台

欢迎搜索及关注: Crossin的编程教室

相关文章
相关标签/搜索