Python浅拷贝与深拷贝(可变对象与不可变对象)

Python浅拷贝与深拷贝(可变对象与不可变对象),第1张

概述第一次遇到深拷贝和浅拷贝的问题是用python在一个for循环中对一个list赋值,使用的语句是 a = b 这个b会不断带入循环,每次计算得到,最后发现list乱七八糟的,后来才发现,python中a=b并不是创建一个a,将b的值赋给它,而是b的地址的一个复制。 后来其实在C#的另一个问题中,也是类似的错误,当然这个问题要复杂的多,因此特地重写了一篇。 我不由自主地会想,浅拷贝与深拷贝的区别是什

第一次遇到深拷贝和浅拷贝的问题是用python在一个for循环中对一个List赋值,使用的语句是

a = b

这个b会不断带入循环,每次计算得到,最后发现List乱七八糟的,后来才发现,python中a=b并不是创建一个a,将b的值赋给它,而是b的地址的一个复制。

后来其实在C#的另一个问题中,也是类似的错误,当然这个问题要复杂的多,因此特地重写了一篇。

我不由自主地会想,浅拷贝与深拷贝的区别是什么,在什么场景下要用到浅拷贝,而什么场景下用深拷贝呢?

1、首先明白什么是浅拷贝和深拷贝

可变对象、不可变对象

  在了解深浅拷贝之前,先了解可变与不可变对象。

  可变对象是指地址所指向的值可变的对象,相反不可变对象就是地址所指向的对象是不可变的。举个例子

  a = 5  a = 6

  这里a赋了两次值,但是第二次赋值时,a的地址并不改变,不同的是,执行语句后,a指向的地方存储的值从5变成了6。因此,a是可变对象。

  那么,这里的‘5’呢?5是一个数字,当你在语句中输入5时,它就一定代表数字5所在的地方,你不能给5再赋值,因此,5是不可变对象。当你试图改变一个不可变对象时,它事实上新生成了一个对象并返回给你。例如,y = y+1,返回的y事实上已经不是原来y的地址了。

  不可变对象包含:int,float,complex,long,str,unicode,tuple。

深浅拷贝语句
import copya=[1,2,3,4,5,[a,b]]#原始对象b=a#赋值,传对象的引用c=copy.copy(a)#对象拷贝,浅拷贝d=copy.deepcopy(a)#对象拷贝,深拷贝print "a=",a,"    ID(a)=",ID(a),"ID(a[5])=",ID(a[5])print "b=",b,"    ID(b)=",ID(b),"ID(b[5])=",ID(b[5])print "c=",c,"    ID(c)=",ID(c),"ID(c[5])=",ID(c[5])print "d=",d,"    ID(d)=",ID(d),"ID(d[5])=",ID(d[5])print "*"*70a.append(6)#修改对象aa[5].append(c)#修改对象a中的[‘a‘,‘b‘]数组对象print "a=","       ID(c)=","            ID(d)=",ID(d[5])

 

 

 a = b 是赋值 *** 作,只是复制一个地址,就是一个地址的两个引用。

copy函数是另起了一个地址,存放新对象,但是只是重新复制了可变对象,不可变对象的地址并不变,因此如果改动不可变对象,copy出的对象仍然会变。

deepcopy则是完完全全生成一个新的对象,源对象中的所有对象都生成一份放在新地址中。

 

2、为什么要有深浅拷贝

其实我一直在用相关的知识,但一直不自知,没有去仔细思考。例如,在一道leetcode题中:

#21.合并两个有序链表#将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 class Solution(object):    def mergeTwoLists(self,l1,l2):        """        :type l1: ListNode        :type l2: ListNode        :rtype: ListNode        """        p = ListNode(0)        ans = p       while(l1 or l2):            if(l1==None):                p.next = l2                return ans.next            elif(l2==None):                p.next = l1                return ans.next            else:                if(l1.val>l2.val):                    p.next = l2                    p = p.next                    l2 = l2.next                else:                    p.next = l1                    p = p.next                    l1 = l1.next        return ans.next

标黄的一行,就是用ans记录p节点的位置,这样p就可以进行迭代,最终返回ans,即返回了头指针,在这里,很容易理解,ans = p *** 作就是使用ans复制了p的地址,并不是复制p的值,也不是指向p,而是,执行了将p所在的地址引用给ans。这就是浅拷贝。

a = b赋值语句通常用来记录b的地址,如果b需要在后续进行迭代 *** 作,a可以记录初始位置。 copy函数进行了浅层拷贝,可以在新对象中对一些元素进行 *** 作和修改,同时,那些常用的不可变对象还是原来的元素,这样的好处至少如下 时间、内存开销更小,效率更高了 用编程思想来类比,不可变对象可以认为类似于全局变量,可变对象可以认为是实例化对象中的局部变量,这样做的好处就显而易见了 deepcopy就是完全全新生成了一个对象,其中所有的对象都是全新,与源对象没关系的,可以随意更改。

3、函数中的传参关系

我们常常把一个对象传入到函数中,函数中也经常对该对象进行 *** 作。虽然有不同的赋值 *** 作,但是其实不同 *** 作之间的含义是不一样的。

def func1(nums):    nums = set(nums)def func2(nums):    nums = [1,5]
l = [1,1,1]
func1(l)
func2(l)

在这个 *** 作中,都在定义的函数中对nums进行了赋值,但是如果实 *** 就会发现,在函数内nums被改变了,但函数运行结束后,l还是等于[1,1]。

这是因为在函数中,nums这个对象代表的只是对l的引用,nums = set(nums)这个语句,是生成了一个set(nums)对象,并将该对象的地址赋给了nums这个引用。并没有将set(nums)的内容覆盖掉nums所在的地址的内容。

 

def func3(nums):    for i in range(len(nums)):            nums[i] = 9def func4(nums):    nums.pop(1)
   nums.append(3)
l = [1,1]func3(l)func4(l)

在这个 *** 作中,会发现l的值都被改变了。这是因为以上无论是针对index赋值还是List *** 作,都是针对nums这个变量所指向的List对象进行的 *** 作,也就是实实在在地 *** 作在了地址上,改变了元素。这事实上是python函数中传参的问题。有一篇博文写的很清楚:python函数参数传递:传值还是传引用。

所以要清楚地记得,python函数中传递的就是对象的地址。当 *** 作针对地址所指的对象进行 *** 作时,就会变,否则当你试图给这个传递的参数赋值时,就会失效。

总结

以上是内存溢出为你收集整理的Python浅拷贝与深拷贝(可变对象与不可变对象)全部内容,希望文章能够帮你解决Python浅拷贝与深拷贝(可变对象与不可变对象)所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/1190821.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-03
下一篇 2022-06-03

发表评论

登录后才能评论

评论列表(0条)

保存