Python中的对象引用、浅拷贝与深拷贝

最近项目中遇到一个Python浅拷贝机制引起的bug,由于对于Python中对象引用、赋值、浅拷贝/深拷贝机制没有足够的认识,导致调试了很久才发现问题,这里简单记录一下相关概念。

在Python的设计哲学中,Python中的每一个东西都是对象,都有一个ob_refcnt变量,这个变量维护着对对象的引用计数,决定着对象的创建与消亡。

所以在Python程序中,一般的赋值操作其实只是将左值指向了右值的引用,并不会创建新的对象,可以通过id函数查看Python中对象在内存中的唯一标识,以list对象为例,如下代码:

>>> alist=[[1,2],3,4]
>>> blist=alist
>>> id(alist);id(blist) #alist/blist实际引用内存中的同一个list对象
140357688098184
140357688098184
>>> blist.append(5)
>>> blist
[[1, 2], 3, 4, 5]
>>> alist
[[1, 2], 3, 4, 5] #由于实际引用同一个list对象,blist增加一个元素后,alist的取值实际上是完全一样的
>>> id(alist);id(blist)
140357688098184
140357688098184

在上面的代码中,将alist的值赋给blist,其实只是把blist指向了alist在内存中的对象,两者引用了同一个list对象,此时如果对blist append一个新元素,由于是指向同一个对象,alist的输出结果一样会变化。

通过slice语法或者copy模块的copy函数,可以实现浅拷贝--

>>> import copy
>>> alist=[[1,2],3,4]
>>> blist=alist[:]
>>> clist=copy.copy(alist)
>>> id(alist);id(blist);id(clist) #alist/blist/clist实际已经指向内存中的不同list对象
140357691858696
140357691897864
140357720939912
>>> id(alist[0]);id(blist[0]);id(clist[0]) #alist[0]/blist[0]/clist[0]三个子对象依然指向内存中的同一个list对象
140357691897800
140357691897800
140357691897800
>>> blist.append(5)
>>> blist
[[1, 2], 3, 4, 5]
>>> alist
[[1, 2], 3, 4] #blist对象值的变更,不会再影响到alist和clist
>>> clist
[[1, 2], 3, 4]
>>> alist[0].append(‘a‘)
>>> alist
[[1, 2, ‘a‘], 3, 4]
>>> blist
[[1, 2, ‘a‘], 3, 4, 5] #由于实际引用同一对象,alist[0]子对象值的变更,也会从blist[0]/clist[0]上体现出来
>>> clist
[[1, 2, ‘a‘], 3, 4]
>>> id(alist[1]);id(blist[1]);id(clist[1])
10919488
10919488
10919488

可以看到blist和clist本身已经是新的list对象,不再引用alist这个list对象,但是三个list中的子对象还是相同的引用,因为python中的浅拷贝只能拷贝父对象,不会拷贝对象内部的子对象。

通过copy模块中的copy.deepcopy函数可以实现深拷贝:

>>> alist=[[1,2],3,4]
>>> blist=copy.deepcopy(alist)
>>> id(alist);id(blist) #alist/blist已经引用内存中不同的list对象
140357692023560
140357691897608
>>> blist.append(5)
>>> blist
[[1, 2], 3, 4, 5]
>>> alist
[[1, 2], 3, 4] #blist取值的变更,不会影响到alist
>>> id(alist[0]);id(blist[0]) #alist{0]/blist[0]两个子对象也已经引用内存中不同的list对象
140357691897864
140357691896136
>>> alist[0].append(‘a‘)
>>> alist
[[1, 2, ‘a‘], 3, 4]
>>> blist
[[1, 2], 3, 4, 5] # alist[0]子对象值的变更,也不会再印象到blist[0]的值
>>> id(alist[1]);id(blist[1])
10919488
10919488

可以看到,通过copy.deepcopy进行拷贝后,alist和blist指向不同的list对象,同时其子对象alist[0]/blist[0]也指向了不同的list对象,但是alist[1]/blist[1]还是指向相同的对象,这是因为3、4在Python中其实是不可变对象,相当于是常量,在Python中不可变对象只会存在唯一一份,所以无论浅拷贝/深拷贝,都是对同一个不可变对象进行的引用。

对于dict/set这些Python类型对象的赋值操作,也会存在类似的浅拷贝/深拷贝的问题,下面再以dict为例贴一下代码:

引用赋值:

>>> adct={‘d‘:{1:2}, 3:4}
>>> bdct=adct
>>> id(adct);id(bdct) #adct/bdct实际引用内存中的同一个dict对象
140357688090760
140357688090760
>>> id(adct[‘d‘]);id(bdct[‘d‘]) #adct[‘d‘]/bdct[‘d‘]两个子对象实际引用内存中的同一个dict对象
140357691897928
140357691897928
>>> bdct[‘d‘].update({‘a‘:‘b‘})
>>> bdct
{‘d‘: {1: 2, ‘a‘: ‘b‘}, 3: 4}
>>> adct
{‘d‘: {1: 2, ‘a‘: ‘b‘}, 3: 4} #由于实际指向同一个子对象,bdct[‘d‘]取值的变更会直接影响到adct的值

copy.copy浅拷贝:

>>> adct={‘d‘:{1:2}, 3:4}
>>> bdct=copy.copy(adct)
>>> id(adct);id(bdct) #adct/bdct引用不同的dict对象
140357688082888
140357720937544
>>> id(adct[‘d‘]);id(bdct[‘d‘]) #adct[‘d‘]/bdct[‘d‘]两个子对象依然指向内存中同一个dict对象
140357688101704
140357688101704
>>> bdct[‘d‘].update({‘a‘:‘b‘})
>>> bdct
{‘d‘: {1: 2, ‘a‘: ‘b‘}, 3: 4}
>>> adct
{‘d‘: {1: 2, ‘a‘: ‘b‘}, 3: 4} #由于实际引用同一个子对象,bdct[‘d‘]子对象值的变更会直接影响到adct的值

copy.deepcopy深拷贝:

>>> adct={‘d‘:{1:2}, 3:4}
>>> bdct=copy.deepcopy(adct)
>>> id(adct);id(bdct) #adct/bdct本身已经引用不同的dict对象
140357691897928
140357688094152
>>> id(adct[‘d‘]);id(bdct[‘d‘]) #adct/bdct的子对象引用了不同的dict子对象
140357688090760
140357688085896
>>> bdct[‘d‘].update({‘a‘:‘b‘})
>>> bdct
{‘d‘: {1: 2, ‘a‘: ‘b‘}, 3: 4}
>>> adct
{‘d‘: {1: 2}, 3: 4} #bdct[‘d‘]子对象的变更不会再影响到adct[‘d‘]的值

原文地址:https://www.cnblogs.com/AcAc-t/p/python_object_ref_shallow_deep_copy.html

时间: 2024-11-10 07:55:15

Python中的对象引用、浅拷贝与深拷贝的相关文章

Python中赋值、浅拷贝与深拷贝

python中关于对象复制有三种类型的使用方式,赋值.浅拷贝与深拷贝.他们既有区别又有联系,刚好最近碰到这一类的问题,研究下. 一.赋值 在python中,对象的赋值就是简单的对象引用,这点和C++不同.如下: list_a = [1,2,3,"hello",["python","C++"]] list_b = list_a 这种情况下,list_b和list_a是一样的,他们指向同一片内存,list_b不过是list_a的别名,是引用. 我们可

关于python中赋值、浅拷贝、深拷贝之间区别的深入分析

大家都知道,在python中复制一个对象有多种方法,其中常用的是赋值.浅拷贝和深拷贝,这三者之间有哪些区别和哪些坑呢? 首先,定义一下: 赋值:  a =1    b =a    a赋值给了b 浅拷贝: a = []  b = a.copy() 或者import copy             b = copy.copy(a) 深拷贝:import copy  a = []   b = copy.deepcopy(a) 未完待续

Python中的赋值,浅拷贝和深拷贝的区别

赋值 内存地址的引用,所有的改变都会同步 测试代码 #coding:utf-8 import copy a=['a','b',1,[1,2,3]] b = a #对象赋值,所有改动都会联动 a.append('d') a[0]='aaa' a[3].append(4) print a print b 运行结果 ['aaa', 'b', 1, [1, 2, 3, 4], 'd'] ['aaa', 'b', 1, [1, 2, 3, 4], 'd'] 浅拷贝 str,num等深浅拷贝都一样,list

python中赋值,浅拷贝,深拷贝的区别

1.首先,对被操作对象分类,对于不可变对象而言,如字符串.数字.tuple等,这三种操作是等价的,都是引用 import copy a='apple'b=ac=copy.copy(a)d=copy.deepcopy(a)print(id(a))print(id(b))print(id(c))print(id(d) 输出: 1840734496184073449618407344961840734496 可见,这四个变量都指向同一块内存地址,即'apple'这个字符串所在的地址 2.对可变对象(或

一入python深似海--浅拷贝与深拷贝

python中有一个模块copy,deepcopy函数用于深拷贝,copy函数用于浅拷贝.要理解浅拷贝,必须先弄清楚python中的引用. 引用 Python中一切都是对象,变量中存放的是对象的引用.这是一个普遍的法则.可以说 Python 没有赋值,只有引用.如,a=1,变量a只是整数对象1的引用. 可变对象与不可变对象及其引用 一.不可变对象 不可变对象包括:数字,字符串,元组. 由于Python中的变量存放的是对象引用,所以对于不可变对象而言,尽管对象本身不可变,但变量的对象引用是可变的.

js中对象的浅拷贝和深拷贝的区别

js中对象的浅拷贝和深拷贝的区别 浅度拷贝:复制一层对象的属性,并不包括对象里面的为引用类型的数据,当改变拷贝的对象里面的引用类型时,源对象也会改变. 深度拷贝:重新开辟一个内存空间,需要递归拷贝对象里的引用,直到子属性都为基本类型.两个对象对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性. 数据的类型: 一般数据(值传递):字符,数值,布尔,undefined 拷贝(复制)时,传递的是值,修改新数据,不会影响老数据 复杂数据(引用传递):对象 拷贝(复制)时,传递的是内存地址的

Python中list的复制及深拷贝与浅拷贝探究

在Python中,经常要对一个list进行复制.对于复制,自然的就有深拷贝与浅拷贝问题.深拷贝与浅拷贝的区别在于,当从原本的list复制出新的list之后,修改其中的任意一个是否会对另一个造成影响,即这两个list在内存中是否储存在同一个区域,这也是区分深拷贝与浅拷贝的重要依据.接下来我们就针对Python中list复制的几种方法,来探究一下其是属于深拷贝还是浅拷贝.弄清楚这个问题,有助于我们在编程中规避错误,减少不必要的调试时间. 一.非拷贝方法--直接赋值 如果用=直接赋值,是非拷贝方法.这

Python对象赋值、浅拷贝、深拷贝

Python中,基本数据类型,理解为常见数据类型:布尔型.整型.浮点型.字符串.列表.元组.字典.集合,随语言不同而不同,但是根据在内存中存储方式的不同,区分开原子类型和容器类型. 对象赋值 对象的赋值都是进行(对象引用传递)/(内存地址传递)/(内存引用),所以当一个对象改变,另一个同步改变. 结合代码思考 ··· will = ["Will", 28, ["Python", "C#", "JavaScript"]] # 元

二、Python开发---8、浅拷贝与深拷贝

浅拷贝与深拷贝 可变(mutable)参数和不可变(immutable)参数 Python中string.tuple和number是不可变对象,而dict.list等是可变对象:不可变对象在进行重新赋值的时候,实际上是将原始值丢弃,将变量指向一个新值:可变对象的可变性实质上是指更改可变对象中的子对象,比如list中的item元素的更改. 下文中讨论的赋值,都是基于上述的条件之上的,即操作的都是可变对象(dict.list),如果操作的是不可变对象(string.tuple.number),则不适