资讯专栏INFORMATION COLUMN

python深拷贝与浅拷贝

ideaa / 403人阅读

摘要:之前关于的作用域赋值参数传递,我们接连谈了几篇文章全菊变量和菊部变量关于函数参数传递,人都错了可变对象与不可变对象今天我们依然要就相关话题继续下去。这是由于它们是不可变对象,不存在被修改的可能,所以拷贝和赋值是一样的。

之前关于 Python 的作用域、赋值、参数传递,我们接连谈了几篇文章:

全菊变量和菊部变量

关于函数参数传递,80%人都错了

可变对象与不可变对象

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

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


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

m 的结果是什么?

正确答案是 [1, 2, [5]] ,这次比上次好点,有 35% 的正确率。

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

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


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

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

现在我们想要再来“复制”一个同样的变量。也许第一个闪过脑中的念头就是:


n = m

但看了前面的文章后你应该知道,这样的 赋值只相当于增加了一个标签,并没有新的对象产生

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 已不是同一个对象 ,对于某个元素的重新赋值不会影响原对象。但是,它们 内部的元素全都是一样的 ,所以对一个可变类型元素的修改,则仍然会反应在原对象中。

(其实这里1、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的编程教室

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/44991.html

相关文章

  • python 拷贝与浅拷贝

    摘要:深拷贝和浅拷贝原始对象赋值,传对象的引用对象拷贝,浅拷贝对象拷贝,深拷贝修改对象修改对象中的数组对象 深拷贝和浅拷贝 import copy a = [1, 2, 3, 4, [a, b]] #原始对象 b = a #赋值,传对象的引用 c = copy.copy(a) #对象拷贝,浅拷贝 d = copy.deepcopy(a) #对象拷贝,深拷贝 a...

    JeOam 评论0 收藏0
  • 面试篇---1 如何区分拷贝与浅拷贝

    摘要:引用数据类型名存在栈内存中,值存在于堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值,我们以上面浅拷贝的例子画个图当进行拷贝时,其实复制的是的引用地址,而并非堆里面的值。 如何区分深拷贝与浅拷贝? 简单来说,就是假设B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝,拿人手短,如果B没变,那就是深拷贝,自食其力。 浅拷贝例子: var a=[0,1...

    jsdt 评论0 收藏0
  • JS中的拷贝与浅拷贝

    摘要:中的深拷贝与浅拷贝说到深浅拷贝的时候就不得不说一下中的变量类型了基本类型按值存放在栈内存中的简单数据段可以直接访问引用类型存放在堆内存中的对象变量保存的是一个指向存放数据位置的指针访问引用类型的值时首先从栈中获取到存放该数据位置的指针然后再 JS中的深拷贝与浅拷贝 说到深浅拷贝的时候就不得不说一下JS中的变量类型了: 基本类型: undefined、null、boolean、numb...

    ARGUS 评论0 收藏0
  • 谈谈拷贝与浅拷贝

    摘要:前言关于深拷贝和浅拷贝其实是两个比较基础的概念,但是我还是想整理一下,因为里面有很多小细节还是很有意思的。那深拷贝就是两者指向不同的内存地址,是真正意义上的拷贝。谈谈是我们经常用到的方法,其实这个方法就是浅拷贝。 前言 关于深拷贝和浅拷贝其实是两个比较基础的概念,但是我还是想整理一下,因为里面有很多小细节还是很有意思的。 深拷贝和浅拷贝的区别 深拷贝和浅拷贝是大家经常听到的两个名词,两...

    Awbeci 评论0 收藏0
  • JS每日一题:拷贝与浅拷贝的区别?如何实现一个拷贝

    摘要:期深拷贝与浅拷贝的区别如何实现一个深拷贝在回答这个问题前,我们先来回顾一下中两大数据类型基本类型引用类型基本类型基本类型就是值类型存放在栈内存中的简单数据段,数据大小确定,内存空间大小可以分配引用类型引用类型存放在堆内存中的对象,变量实际保 20190311期 深拷贝与浅拷贝的区别?如何实现一个深拷贝 在回答这个问题前,我们先来回顾一下JS中两大数据类型 基本类型 Undefined...

    MiracleWong 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<