Python copy模块浅拷贝和深拷贝

在开发中,经常涉及到数据的传递,在数据传递使用的过程中,可能会对数据进行修改。

对数据进行处理后,如果在后面的代码中,即需要使用修改之前的数据,也需要使用修改之后的数据,就要在修改前对数据进行拷贝。

拷贝数据后,有两份数据,只修改其中一份数据,不修改另一份数据,到最后依然能保留修改前的数据。

一、Python 实现数据拷贝的方法

# coding=utf-8
import copybase = ['a', 'b', 'c', 'd', 'e']
# 切片
bak1 = base[:]
print("bak1: ", bak1)
# list工厂函数
bak2 = list(base)
print("bak2: ", bak2)
# python list对象的copy方法
bak3 = base.copy()
print("bak3: ", bak3)
# copy模块的copy方法
bak4 = copy.copy(base)
print("bak4: ", bak4)

运行结果:

bak1:  ['a', 'b', 'c', 'd', 'e']
bak2:  ['a', 'b', 'c', 'd', 'e']
bak3:  ['a', 'b', 'c', 'd', 'e']
bak4:  ['a', 'b', 'c', 'd', 'e']

上面的代码使用了四种方式来对数据进行拷贝,这些方法都可以用来拷贝数据,结果都一样。

1.切片

对需要拷贝的数据进行切片处理,返回的结果相当于拷贝了一份数据。

2.工厂方法

使用 python 的工厂函数 list 来拷贝数据。(python的工厂函数是比较特殊的,即是类也是函数,关于工厂函数的理解可以另行扩展一下)

拷贝列表时使用 list,如果拷贝字符串则将上面的 list 换成 str ,以此类推。

3.list对象的copy方法

python 中的 list 实现了 copy 方法,在拷贝列表时可以直接使用。这里需要注意,比如 str 没有实现 copy 方法,拷贝字符串时使用其他方法拷贝。

4.copy模块的copy方法

在 Python 标准库中有一个 copy 模块,可以使用 copy 模块的 copy() 方法来拷贝数据,copy 模块可以拷贝所有类型的数据。

二、拷贝的数据被修改

import copyson = ['python', 'copy']
base = ['a', 'b', 'c', 'd', 'e', son]
bak1 = base[:]
print("bak1: ", bak1)
bak2 = list(base)
print("bak2: ", bak2)
bak3 = base.copy()
print("bak3: ", bak3)
bak4 = copy.copy(base)
print("bak4: ", bak4)
print('-' * 20, '分割线', '-' * 20)
son[0] = 'PYTHON'
son[1] = 'COPY'
print('base: ', base)
print("bak1: ", bak1)
print("bak2: ", bak2)
print("bak3: ", bak3)
print("bak4: ", bak4)

运行结果:

bak1:  ['a', 'b', 'c', 'd', 'e', ['python', 'copy']]
bak2:  ['a', 'b', 'c', 'd', 'e', ['python', 'copy']]
bak3:  ['a', 'b', 'c', 'd', 'e', ['python', 'copy']]
bak4:  ['a', 'b', 'c', 'd', 'e', ['python', 'copy']]
-------------------- 分割线 --------------------
base:  ['a', 'b', 'c', 'd', 'e', ['PYTHON', 'COPY']]
bak1:  ['a', 'b', 'c', 'd', 'e', ['PYTHON', 'COPY']]
bak2:  ['a', 'b', 'c', 'd', 'e', ['PYTHON', 'COPY']]
bak3:  ['a', 'b', 'c', 'd', 'e', ['PYTHON', 'COPY']]
bak4:  ['a', 'b', 'c', 'd', 'e', ['PYTHON', 'COPY']]

在实际工作中,数据的嵌套层数是很多的,通常会嵌套好几层。上面就在 base 列表中嵌套了一个 son 子列表。

用上面的四种拷贝方法拷贝 base 列表,然后修改 base 列表中的子列表 son 。重新打印这几个列表,发现不仅 base 列表被修改了,拷贝的列表也全部被修改了。

现在的需求是拷贝一份数据,修改一份保留一份,如果两份数据都被修改,是不符合需求的。

上面的四种拷贝方法都被称为浅拷贝(相对深拷贝而言),浅拷贝 Python 中的可变对象,如果数据中嵌套了可变对象,修改嵌套的可变对象,所有拷贝的数据都会一起被修改。

三、Python 可变对象和不可变对象

在 Python 中,所有的数据都是对象,无论是数字,字符串,元组,列表,字典,还是函数,类,甚至是模块。

不可变对象:

int, str, tuple 等类型的数据是不可变对象,不可变对象的特性是数据不可被修改。

a = 'a'
print(id(a))
a = 'b'
print(id(a))

运行结果:

1543912659912
1543912658232

如果对不可变对象修改,其实不是修改变量对象,而是重新创建一个同名的变量对象。可以通过 id 函数来判断,id 不一样就证明已经不是同一个变量了。

可变对象:

list, set,dict 等类型的数据是可变对象,相对于不可变对象而言,可变对象的数据可以被修改,修改之后还是同一个id。

base = [1, 2, 3]
print(id(base))
base[0] = 100
print(base)
print(id(base))

运行结果:

2182371173000
[100, 2, 3]
2182371173000

对可变对象进行修改,修改后还是同一个对象,只是可变对象里面的元素指向了不同的数据,这种指向是通过引用的方式来实现的。

上面的代码是对列表进行修改,如果对元组这样修改,代码会报错,就是因为可变对象和不可变对象的区别。

四、Python 中的引用和引用传递

在 Python 程序中,每个对象都会在内存中开辟一块空间来保存该对象,该对象在内存中所在位置的地址被称为引用。

在编写代码时,定义的变量名实际是定义指向对象的地址引用名。

我们定义一个列表时,变量名是列表的名字,这个名字指向内存中的一块空间。这个列表里有多个元素,表示这块内存空间中,保存着多个元素的引用。

1. 修改引用

当修改列表的元素时,其实是修改列表中的引用。

list_a = [1, 2, 3]
list_a[2] = 30
print('list_a: ', list_a)

运行结果:

list_a:  [1, 2, 30]

例如,修改 list_a 中的第三个元素,其实是修改第三个元素的引用(这块内存指向的对象)。如下图:

2. 引用传递(拷贝)

当拷贝列表时,其实是拷贝列表中的引用。

list_b = [1, 2, 3]
list_c = list_b.copy()
print('list_c: ', list_c)

运行结果:

list_c:  [1, 2, 3]

例如,拷贝 list_b 到 list_c,其实是给 list_c 新开辟一块内存,然后拷贝一份 list_b 的引用给 list_c ,并不是将 list_b 指向的对象拷贝一份。如下图:

注意:这里不是将 list_b 赋值给 list_c,那样的结果是 list_b 指向 [1, 2, 3] ,list_c 指向 list_b,是引用关系,而不是拷贝关系。上面列举拷贝的方法时,没有将赋值列为拷贝方法,因为赋值是引用的传递,而不是拷贝。

五、浅拷贝时数据被修改

1. 拷贝后修改引用(数据无嵌套)

import copylist_b = [1, 2, 3]
list_c = copy.copy(list_b)
list_b[2] = 30
print('list_b: ', list_b)
print('list_c: ', list_c)

运行结果:

list_b:  [1, 2, 30]
list_c:  [1, 2, 3]

使用 copy.copy() 方法拷贝 list_b 到 list_c,然后修改 list_b 中的引用关系,这样, list_c 不会被修改。如下图:

2.嵌套列表的拷贝

import copysub = [2, 3]
list_d = [1, sub]
list_e = copy.copy(list_d)
print('list_d: ', list_d)
print('list_e: ', list_e)

运行结果:

list_d:  [1, [2, 3]]
list_e:  [1, [2, 3]]

对于嵌套的列表,拷贝 list_d 到 list_e,也是拷贝一份 list_d 的引用给 list_e ,与不嵌套的相同。

这里需要特别注意,在浅拷贝嵌套的列表时,只会拷贝最上层的引用,对于子列表的引用,不会拷贝。如下图:

3.拷贝的列表随原列表一起被修改

import copysub = [2, 3]
list_d = [1, sub]
list_e = copy.copy(list_d)
list_d[1][1] = 30
print('list_d: ', list_d)
print('list_e: ', list_e)

运行结果:

list_d:  [1, [2, 30]]
list_e:  [1, [2, 30]]

拷贝 list_d 到 list_e,由于没有拷贝子列表的引用 ,当修改子列时, list_d 和 list_e 都引用了子列表 sub,所以 list_d 和 list_e都会被修改。如下图:

拷贝数据后,修改其中一个,另一个也跟着被修改,原因就是浅拷贝中,只拷贝了最外层的引用。当修改内层的引用时,所有外层的引用不变,都会指向修改后的结果。

两份数据都被修改,这就是浅拷贝中存在的问题,需要使用深拷贝来解决。

六、深拷贝保证数据不会被修改

import copysub = [2, 3]
list_d = [1, sub]
list_f = copy.deepcopy(list_d)
list_d[1][1] = 30
print('list_d: ', list_d)
print('list_e: ', list_f)

运行结果:

list_d:  [1, [2, 30]]
list_e:  [1, [2, 3]]

使用 copy 模块的 deepcopy() 方法,在拷贝数据时,会递归地拷贝数据中所有嵌套的引用。

使用 deepcopy() 拷贝 list_d 到 list_f ,然后修改 list_d 中子列表的引用,不会对 list_f 产生影响,所以 list_f 不会被修改。如下图:(可以和上面的图进行对比)

这样,就可以达到复制数据,一份修改,一份不修改的目的。

在工作中,这种情况不是特别多,所以出现的时候很容易掉坑。比如说,有一个复杂的列表(字典、列表多层嵌套),第一次获取数据写入数据库,后面每次获取数据都要与上一次的数据对比去重,然后把本次获取的数据覆盖数据库中的数据。这就是获取数据,修改数据,最后还需要使用修改前的数据。这时候用浅拷贝,极易出错,对于较大的数据(如一个1M大的json数据),出错了还不易发现错误。

为了解决和避免这种错误,可以使用深拷贝 deepcopy()。

在Python中,浅拷贝消耗的内存和运行效率都优于深拷贝,所以默认的拷贝都是浅拷贝。

对可能需要使用深拷贝的情况,要特别留意,使用深拷贝,避免出错。

Python copy模块浅拷贝和深拷贝相关推荐

  1. Python中的浅拷贝和深拷贝(一看就懂!!!)

    浅拷贝和深拷贝一直傻傻的分不清,甚至有时候会理解反.今天就简单总结一下python中的浅拷贝和深拷贝. 总结 最直观的理解就是: 1.深拷贝,拷贝的程度深,自己新开辟了一块内存,将被拷贝内容全部拷贝过 ...

  2. python中的浅拷贝与深拷贝——copy()

    1.copy()方法用来拷贝列表元素 1 a = [1, 2, 3] 2 # 拷贝 3 b = a.copy() 4 print('a = ', a, '\n', 'b = ', b) 5 6 # 执 ...

  3. Python赋值、浅拷贝、深拷贝

    一.赋值(assignment) >>> a = [1, 2, 3] >>> b = a >>> print(id(a), id(b), sep= ...

  4. Python 赋值、浅拷贝、深拷贝的区别?

    http://songlee24.github.io/2014/08/15/python-FAQ-02/ 在写Python过程中,经常会遇到对象的拷贝,如果不理解浅拷贝和深拷贝的概念,你的代码就可能出 ...

  5. 拷贝Python对象、浅拷贝、深拷贝

    浅拷贝和深拷贝在C++中出现和使用的较多,python也有相应的用法. test1 = ['a','c','b','d','f'] print id(test1) test2 = test1 prin ...

  6. python引用、浅拷贝、深拷贝

    2019独角兽企业重金招聘Python工程师标准>>> # -*- coding: utf-8 -*-"""对象赋值与copy Python中的对象之间 ...

  7. python深浅拷贝 面试_[面试题二]百度资深面试官:python赋值、浅拷贝与深拷贝

    内存管理相关的面试一直是面试中的重点考察内容.赋值.深拷贝与浅拷贝是日常编码工作中,需要时常关注的知识点.不小心可是会写出bug哦- 赋值 先来看一段代码,下面两段代码的输出结果会是什么呢? # ca ...

  8. Python —对象的浅拷贝和深拷贝

    变量的赋值操作 只是形成两个变量,实际还是指向同一个对象. 浅拷贝 Python 拷贝一般都是浅拷贝(copy.copy).拷贝时,对象包含的子对象内容不拷贝.因此,源对象和拷贝对象会引用同一个子对象 ...

  9. Python中的浅拷贝与深拷贝

    编者注:本文主要参考了<Python核心编程(第二版)> 以下都是参考资料后,我自己的理解,如有错误希望大家不吝赐教. 大家有没有遇到这样一种情况,对象赋值后,对其中一个变量进行修改,另外 ...

最新文章

  1. 【部署类】专题:消息队列MQ、进程守护Supervisor
  2. java jms过时了么_JMS Message正在从Hibernate获取过时的数据
  3. observable.unsubscribeOn(Schedulers.io())
  4. electron/nodejs实现调用golang函数
  5. 1106 冒泡排序的语法树
  6. 初步了解CUDA(零)
  7. javascript中数组循环的方式
  8. c语言中输出值作用,printf在c语言中什么意思
  9. 苹果cms V10模板|首涂第二十五套精简宽屏主题模板破解带后台无加密版
  10. 关于APP接口设计 注意事项
  11. IntelliJ Keymap
  12. 三、EasyExcel实现数据填充
  13. 网络安全之黑客入侵的步骤
  14. 如何调整网站竞价关键词
  15. 持续学习:(Elastic Weight Consolidation, EWC)Overcoming Catastrophic Forgetting in Neural Network
  16. 按键精灵制作自用游戏脚本所需的简单操作
  17. CSDN博客创建目录的方法
  18. 【图像处理】轮廓二阶矩计算目标中心-计算目标中心位置方法3
  19. 网络安全之身份认证---双因子身份认证技术
  20. 公众号markdown排版神器(md转微信html)

热门文章

  1. 大白话Docker入门(二)
  2. hdu 5017 Ellipsoid(西安网络赛 1011)
  3. 开源 免费 java CMS - FreeCMS1.5-数据对象-job
  4. 《Maintainable JavaScript》读书笔记
  5. 一个flash网页图片播放器
  6. 如何防止网页Demo被那些赖账的客户盗用
  7. 面试精讲之面试考点及大厂真题 - 分布式专栏 08 Redis中有哪些数据结构及底层实现原理
  8. 容器编排技术 -- Kubernetes kubectl create secret docker-registry 命令详解
  9. 容器编排技术 -- 本地运行Kubrenetes v1.0
  10. MinIO Docker 快速入门 ​​​​​​​