Python的复制

Python的复制

 对于Python的复制,相信很多人都有疑惑,因为Python的默认复制是浅复制实现的,因此经常出现各种离奇的问题。要理解好Python的复制机制,需要先理解Python的变量和类型。

不可变对象与可变对象
  在Python中,对象可以分为如下两种类型:不可变对象和可变对象。其中不可变对象包括int,float,long,str和tuple等,可变对象则包括list、dict和set等,值得注意的是:这里说的不可变对象中的不可变指的是值不可变。
  对于不可变类型的变量,如果要更改变量,则会创建一个新值,把变量绑定到新值上,而旧值如果没有被引用就等待垃圾回收。另外,不可变的类型可以计算hash值,作为字典的key。可变类型数据对对象操作的时候,不需要再在其他地方申请内存,只需要在此对象后面连续申请(+/-)即可,也就是它的内存地址会保持不变,但区域会变长或者变短。这个机制可通过如下代码理解:

num = 1
print id(num)
num += 2
print id(num)
l = [1, 2, 3]
print id(l)
l.append(4)
print id(l)

  该例子中num的两个id是不一样的,表明num变量在不同的取值下指向的内存地址不一样;而l的两个id是一样的,表明l变量在不同的取值下指向的内存地址是一样的。

变量和对象
  在Python中,非常重要的一点:“变量无类型,对象有类型”。此点的理解是:Python中的变量是无类型的,但Python是区分类型的。Python的每一个变量其实都是指向内存对象的一个指针,变量都是值得引用,而类型只与对象有关。总结来说:在Python中,类型是属于对象的,而不是变量, 变量和对象是分离的,对象是内存中储存数据的实体,变量则是指向对象的指针。总的来说,Python中类型是属于对象的,变量并没有类型,变量和对象其实是相互分离的,对象是内存中存储数据的实体,而变量只是指向对象的指针。
  在不可变对象和可变对象的代码中,可能读者有个疑问,如下代码会改变变量l的id,

l = [1, 2, 3]
print id(l)
l = [1, 2, 3, 4]
print id(l)

的确,上述代码l的两个id不一样,这其中的区别在于开始l指向了[1, 2, 3]的list的对象,当使用append方法时,由于l是可变对象,因此不需要重新再内存中创建新的对象,只需在原来的内存位置增加空间即可。而当使用“=”赋值一个list时,其实在内存是先生成了一个[1, 2, 3, 4]的list对象,然后让l指向该对象,因此出现两个id不一样。

函数的参数传递方式
  对于函数的参数传递方式,其实只要理解好变量和对象这一节的内容就很容易理解了,因为Python中变量都是指向对象的指针,而对象才是内存中存储数据的实体,因此函数的参数传递实质上就是让变量指向传入的对象而已。
  理解好下面的代码就基本理解了函数的参数传递方式了,下面逐一讲解结果以及原因,

def fun_add_num(num):
    num += 2

def fun_add_list(l):
    l.append(4)

def fun_assign_list(l_1):
    l_1 = [1, 2, 3, 4]

if "__main__" == __name__:
    num = 1
    print num
    fun_add_num(num)
    print num

    l = [1, 2, 3]
    print l
    fun_add_list(l)
    print l

    l_1 = [1, 2, 3]
    print l_1
    fun_assign_list(l_1)
    print l_1

  (1)在main函数中两次输出的num的值均为1,在fun_add_num中的num开始是指向1这个int对象的,但当执行加法时,由于int对象是不可变对象,因此在fun_add_num中的num变量就变为指向3这个int对象,而main中的num变量依旧指向1这个对象,因此两次输出的num的值均为1
  (2)在main函数中两次输出的l的值依次为[1, 2, 3],[1, 2, 3, 4],在fun_add_list中的l开始是指向[1, 2, 3]这个list对象,当使用append方法时,需要改变该对象的值,由于list是可变对象,因此只需要在其后面增加内存空间即可,从而得到这样的结果。
  (3)在main函数中两次输出的l_1的值依次为[1, 2, 3],[1, 2, 3],在fun_assign_list中的l_1开始时指向[1, 2, 3]对象,该对象与main函数中l_1指向的对象一致,但当使用赋值时,[1, 2, 3, 4]这个list会在内存中重新申请空间存储,因此此时在在fun_assign_list中的l_1改为指向[1, 2, 3, 4]这个list对象,该对象与main函数中生成的[1, 2, 3]对象没有任何关系,因此出现这样的结果。

浅拷贝和深拷贝
  本人这部分主要参考了下面这个博客,读者可以自行前往学习:
  http://www.cnblogs.com/wilber2013/p/4645353.html
  (1)变量赋值

father = ["father", ["python", "c++"]]
son = father
print id(father)
print father
print [id(ele) for ele in father]
print id(son)
print son 
print [id(ele) for ele in son]

father[0] = "father_0"
father[1].append("java")
print id(father)
print father
print [id(ele) for ele in father]
print id(son)
print son 
print [id(ele) for ele in son]

运行结果:
这里写图片描述
解析:
  使用变量赋值的方式进行复制时,实质上就是让father变量和son变量指向同一个内存地址(这点可以从两者的id一致验证),因此当改变father指向的list对象的元素时,son同样会改变。
  (2)浅拷贝

import copy
father = ["father", ["python", "c++"]]
son = copy.copy(father)
print id(father)
print father
print [id(ele) for ele in father]
print id(son)
print son
print [id(ele) for ele in son]

father[0] = "father_0"
father[1].append("java")
print id(father)
print father
print [id(ele) for ele in father]
print id(son)
print son
print [id(ele) for ele in son]

运行结果:
这里写图片描述
解析:
  当使用浅复制的方式进行复制时,可以看到father和son两个变量指向的内存地址是不一样的,但两个内存地址的对象的元素指向的地址是一致的。由于”father”的类型是str,为不可变对象,当对象的值需要改变时,重新申请内存空间放置,因此此时father[0]变量指向的地址改变了,而son[0]依旧没有改变,所以father[0]变量指向的内存地址的对象为”father_0”,而son[0]则依旧指向”father”对象。由于father[1]指向的是list对象,该对象为可变对象,因此使用append等方法修改该对象值时只需在原内存空间上增加空间,father[1]指向的地址不需要变,由于son[1]与father[1]指向的地址是一致的,因此会出现father[1]和son[1]同时改变的现象。
  (3)深拷贝

import copy
father = ["father", ["python", "c++"]]
son = copy.deepcopy(father)
print id(father)
print father
print [id(ele) for ele in father]
print id(son)
print son
print [id(ele) for ele in son]

father[0] = "father_0"
father[1].append("java")
print id(father)
print father
print [id(ele) for ele in father]
print id(son)
print son
print [id(ele) for ele in son]

运行结果:
这里写图片描述
解析:
  当使用深复制进行复制时,不仅father和son变量指向的地址不一致,就连father和son的元素指向的地址也不一定一致,不一定一致的原因在于,在python中,相同值得不可变对象的内存地址均一致,因此导致开始时father[0]和son[0]指向了相同的内存地址,但当father[0]指向的对象的值需要改变时,father[0]指向了另外一个地址,此时father[0]和son[0]指向的内存地址就不一致了;而相同值得可变对象的内存则可以不一致,因此开始时father[1]和son[1]指向的内存地址不一致,当father[1]指向的内存对象值改变时,不会影响到son[1]指向的内存对象值。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值