浅谈Python浅拷贝、深拷贝及引用机制

这礼拜碰到一些问题,然后意识到基础知识一段时间没巩固的话,还是有遗忘的部分,还是需要温习,这里做份笔记,记录一下

前续

先简单描述下碰到的题目,要求是写出2个print的结果

可以看到,a指向了一个列表list对象,在Python中,这样的赋值语句,其实内部含义是指a指向这个list所在内存地址,可以看作类似指针的概念。

而b,注意,他是把a对象包裹进一个list,并且乘以5,所以b的样子应该是一个大list,里面元素都是a

而当a对象进行了append操作后,其实,隐含的意思是,内存中的这个list进行了修改,所有对此对象进行引用的对象,都会发生改变

我将a的id打印出来,并且,同时打印b这个对象中所包含的元素a的id,这样可以看到,在b这个list中,每个元素的id,和a是一样的

我们可以看到,a对象的id(内存地址)为10892296,虽然b把a包裹进了新的list,但是,这个元素引用的,还是相同地址的对象,可以用下图来解释

之后,我们对a进行了append操作,由于list是一个可变对象,所以,他的内存地址并没有改变,但是,对于内存中这个地址的引用的所有对象,都会被一同改变可以从上面测试图分割线下半部分看出来.

由此,引出了对Python引用机制和浅复制及深复制的复习

Python的引用机制

引用机制案例1

由上面的例子,我们可以看到,python的引用传递,最终结果是让2个对象都引用内存中同一块区域的内容

所以我们来看一下下面的例子

B通过A,同样引用了id为17446024的地址的内容,2者的id(内存地址)都是一毛一样的

所以,通过A的操作  A[0]=3  或是   A[3].append(6)  ,都会对这块内存中的内容进行修改(因为list是可变对象,所以内存地址并不会改变,这个后面再讲)

这个是最基本的引用案例 (另外说句,由于A和B都指向了同一块内存地址,所以通过B修改的内容,也能反映到A上面去)

引用机制案例2

我们再来看一个案例

看题目貌似是会把元素2替换成本身这个列表,结果也许应该是 A=[1,[1,2,3],3]

但其实并不是!!你可以看到,红框中部分,中间有无限多个嵌套

为什么会这样呢?

其实是因为,A指向的是[1,2,3]这个列表,在这个例子中,只是把A的第2个元素,指向了A对象本身,所以说,只是A的结构发生了变化!但是,A还是指向那个对象

我们可以通过打印A的id来看,他的指向是没有变的!!

来看一下,A的指向并没有变

那如果我们要达到最后输出效果是 [1,[1,2,3],3]的效果,应该如何来操作呢?

这里,我们就要用到浅复制了,用法可以如下

浅复制和深复制

浅复制

现在,就来说说浅复制和深复制,上面的方法实际上只是进行了浅复制,shallow copy,含义是他是对原来引用的对象进行了复制,但是不再引用同一对象地址

看下面的例子,B通过 B = A[:] 操作,来进行了浅复制,你可以看到,浅复制之后,A和B引用的内存地址已经是不同的了
但是,A和B内部的元素的引用地址,还是相同的,这点要非常注意!是有区别的!!!

A和B的引用内存地址的不同,带来的效果是,你在B上面进行的操作,并不会影响到A。

浅拷贝归纳:

所以浅拷贝,可以归纳为,复制一份引用,新的对象和原来的对象的引用被区分开,但是内部元素的地址引用还是相同的

但是浅复制也会有问题,问题在哪里呢?就是碰到有嵌套的情况,比如下面的情况可以看到,我给B赋值了一份A的浅复制,这样A和B的id(内存地址)就不一样了。

所以,当我修改A[0]=8的时候,B不会被影响到,因为他们A和B两者是独立的引用,但是这里中间有一个嵌套的列表 [4,5,6]
这个[4,5,6]我们可以理解为:A和B还共同引用着,也就是对于A和B的第二个元素来说,他们还是指向同一块内存地址的。

另外要说一句,由于int是不可变类型,所以,把A[0]修改成8之后,他的引用地址就变了!就和B[0]这个元素的引用区分开了。

深复制

那如何面对这样的情况呢?就要用到python模块里面的copy模块了

copy模块有2个功能

1: copy.copy(你要复制的对象) : 这个是浅拷贝,和前面对list进行的 [:] 操作性质是一样的

2: copy.deepcopy(你要复制的对象) : 这个是深拷贝,他除了和浅拷贝一样,会新生成一份对象的引用,另外对于内部的元素,都会新生成引用,以独立分开.

看下面的例子,当你给B赋值一份A的深复制之后,他俩可以说是完全独立开了,无论你修改的是A里面的不可变元素,还是修改A里面嵌套的可变元素,结果都不会影响到B

我的理解是:深复制可以称之为递归拷贝,他会把所有嵌套的可变元素都拷贝一下,然后独立引用出来.

深复制归纳:

深复制的效果,除了和浅复制一样,将对象的引用新生成一份引用之外,内部所有嵌套的元素,他都会帮你一一独立开.

自己画了2张图,以表示浅复制和深复制的效果区别

需要说明的是,虽然浅复制之后,列表内不可变元素的引用地址还是相同的,但是,正因为他们是不可变元素,所以,其中任意不可变元素被改变之后,引用地址都会是新的,而不会影响到原来的引用地址。

总结

所以,到这里,浅复制和深复制的机制,基本上理解了。

另外还有特殊情况需要说明

对于不可变类型:int, str, tuple, float 这样的元素来说,没有拷贝这个说法,他们被修改之后,引用地址就是直接改变了,如下面

但是,如果不可变类型内部有嵌套的可变类型的时候,还是可以使用深复制的

另外要提醒一句,平时我们用的最多的直接赋值(或者可以说是直接传递引用)的方法,比如下面的例子

他是将a和b两个可变元素同时指向一个内存地址,所以,任何改变都是波及到a和b的

最后

可变类型:list , set , dict

不可变类型:int, str , float , tuple

浅复制方法:[:] , copy.copy() ,  使用工厂函数(list/dir/set)

深复制方法:copy.deepcopy()

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • python 函数传参之传值还是传引用的分析

    首先还是应该科普下函数参数传递机制,传值和传引用是什么意思? 函数参数传递机制问题在本质上是调用函数(过程)和被调用函数(过程)在调用发生时进行通信的方法问题.基本的参数传递机制有两种:值传递和引用传递. 值传递(passl-by-value)过程中,被调函数的形式参数作为被调函数的局部变量处理,即在堆栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本.值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值. 引用传递(pass-

  • Python中在脚本中引用其他文件函数的实现方法

    在导入文件的时候,Python只搜索当前脚本所在的目录,加载(entry-point)入口脚本运行目录和sys.path中包含的路径例如包的安装地址.所以如果要在当前脚本引用其他文件,除了将文件放在和脚本同一目录下,还有以下几种方法, 1. 将文件所在位置添加到sys.path中 import sys sys.path.insert(0, '/path/to/application/app/folder') # or sys.path.append('/path/to/application/a

  • python函数缺省值与引用学习笔记分享

    复制代码 代码如下: import random, stringclass C(object):    passdef dangerFunction(msg, l = [], b = {}, c = C()):    print msg, '-'*10    print l, b, c.__dict__    l.append(1)    b[random.choice(string.ascii_lowercase)] = ''    c.__dict__[random.choice(strin

  • Python中的引用和拷贝浅析

    If an object's value can be modified, the object is said to be mutable. If the value cannot be modified,the object is said to be immutable. mutable 可变类型,例如 list,set,自定义类型(等价于C#中的引用类型): immutable 不可变类型,例如string,numbers等(等价于C#中的值类型): 一.引用和拷贝(references

  • python共享引用(多个变量引用)示例代码

    复制代码 代码如下: a = 3b = a 先上图(图1)吧,大家一看就一目了然了: 变量名和对象,在运行赋值语句b = a之后,变量a,b都指向了对象3的内存空间.假设这时执行 a = 'python', a将指向刚创建的字符串对象.我们再来试试这种情况: 复制代码 代码如下: >>>list_1 = [1,2,3,4]>>>list_2 = list_1>>>list_2>>>list_1[0] = 'python'>>

  • python中引用与复制用法实例分析

    本文实例讲述了python中引用与复制用法.分享给大家供大家参考.具体分析如下: 在python中,任何不可变对象是传值的,而可变对象是传引用的. 不管是向函数传递参数或者是任何形式的对象复制来说,不可变对象(比如整数,字符串)被真正复制,而可变对象只是复制了一个对他们的引用,即在内存中只有一份对象,而引用两份.   a=b 这样的赋值,就会创建对b的引用,对于象数字和字符串这样的不可变的对象,这种赋值实际是创建了b的一个副本 >>> a='hello' >>> b=a

  • python复制与引用用法分析

    本文实例讲述了python复制与引用用法.分享给大家供大家参考.具体分析如下: 简单复制是引用 a=[1,23,4] b=a #这是引用 b.append(2323) print(a,b) #([1, 23, 4, 2323], [1, 23, 4, 2323]) 使用copy.copy进行浅拷贝 import copy c=copy.copy(b)#拷贝 c.append(1) print(b,c)#([1, 23, 4, 2323], [1, 23, 4, 2323, 1]) list1=[

  • 从零学Python之引用和类属性的初步理解

    Python是一种解释型.面向对象.动态数据类型的高级程序设计语言.自从20世纪90年代初Python语言诞生至今,它逐渐被广泛应用于处理系统管理任务和Web编程.Python已经成为最受欢迎的程序设计语言之一.2011年1月,它被TIOBE编程语言排行榜评为2010年度语言.自从2004年以后,python的使用率是呈线性增长. Python在设计上坚持了清晰划一的风格,这使得Python成为一门易读.易维护,并且被大量用户所欢迎的.用途广泛的语言. 鉴于以上各种优点,忍不住对Python进行

  • python dict 字典 以及 赋值 引用的一些实例(详解)

    最近在做一个很大的数据库方面的东东,要用到根据数值来查找,于是想到了python中的字典,平时没用过dict这个东东 用的最多的还是 list 和 tuple (网上查 用法一大堆) 看了一下创建字典的方法: 方法1: dict = {'name': 'earth', 'port': 80} 方法2: fdict = dict((['x', 1], ['y', 2])) 方法3: ddict = {}.fromkeys(('x', 'y'), -1) 都实验了一下这些方法,发现不好用,做不出来自

  • Python引用传值概念与用法实例小结

    本文实例讲述了Python引用传值概念与用法.分享给大家供大家参考,具体如下: Python函数的参数传值使用的是引用传值,也就是说传的是参数的内存地址值,因此在函数中改变参数的值,函数外也会改变. 这里需要注意的是如果传的参数类型是不可改变的,如String类型.元组类型,函数内如需改变参数的值,则相当于重新新建了一个对象. # 添加了一个string类型的元素添加到末尾 def ChangeList(lis): lis.append('hello i am the addone') prin

随机推荐