最新消息:yaf表单扩展中新增加了浮点数、日期和集合的校验。php yaf框架扩展实践三——表单

python中的深拷贝和浅拷贝理解

Python 13803浏览 8评论

在python中,对象赋值实际上是对象的引用。当创建一个对象,然后把它赋给另一个变量的时候,python并没有拷贝这个对象,而只是拷贝了这个对象的引用。以下分两个思路来分别理解浅拷贝和深拷贝:

  • 利用切片操作和工厂方法list方法拷贝
  • 利用copy中的deepcopy方法进行拷贝

1、利用切片操作和工厂方法list方法拷贝

代码场景:有一个小伙jack,tom通过切片操作拷贝jack,anny通过工厂方法拷贝jack。

>>> jack = ['jack', ['age', 20]]
>>> tom = jack[:]
>>> anny = list(jack)

来看下三者的id值:

>>> print id(jack), id(tom), id(anny)
144846988 144977164 144977388

从id值来看,三者是不同的对象。为tom和anny重新命名为各自的名称:

>>> tom[0] = 'tom'
>>> anny[0] = 'anny'
>>> print jack, tom, anny
['jack', ['age', 20]] ['tom', ['age', 20]] ['anny', ['age', 20]]

从这里来看一切正常,可是anny只有18岁,重新为anny定义岁数。

>>> anny[1][1] = 18
>>> print jack, tom, anny
['jack', ['age', 18]] ['tom', ['age', 18]] ['anny', ['age', 18]]

这时候奇怪的事情发生了,jack、tom、anny的岁数都发生了改变,都变成了18了。jack、tom、anny他们应当都是不同的对象,怎么会互相影响呢?看下jack,tom,anny的内部元素每个元素id:

>>> [id(x) for x in jack]
[3073896320L, 3073777580L]
>>> [id(x) for x in tom]
[144870744, 3073777580L]
>>> [id(x) for x in anny]
[144977344, 3073777580L]

恍然大悟,原来jack、tom、anny的岁数元素指向的是同一个元素。修改了其中一个,当然影响其他人了。那为什么修改名称没影响呢?原来在python中字符串不可以修改,所以在为tom和anny重新命名的时候,会重新创建一个’tom’和’anny’对象,替换旧的’jack’对象。为了便于理解,我画了一个草图:

python浅拷贝图解

python浅拷贝图解

2、利用copy中的deepcopy方法进行拷贝

为了让他们之间不互相影响,用deepcopy来试试

>>> jack = ['jack', ['age', '20']]
>>> import copy
>>> tom = copy.deepcopy(jack)
>>> anny = copy.deepcopy(jack)

根据第一个思路进行重命名,重定岁数操作:

>>> tom[0] = 'tom'
>>> anny[0] = 'anny'
>>> print jack, tom, anny
['jack', ['age', '20']] ['tom', ['age', '20']] ['anny', ['age', '20']]
>>> anny[1][1] = 18
>>> print jack, tom, anny
['jack', ['age', '20']] ['tom', ['age', '20']] ['anny', ['age', 18]]

这时候他们之间就不会互相影响了。打印出每个人的内部元素每个id:

>>> [id(x) for x in jack]
[139132064, 3073507244L]
>>> [id(x) for x in tom]
[139137464, 139132204]
>>> [id(x) for x in anny]
[139141632, 139157548]

他们的内部元素也都指向了不同的对象。

小结:

思路一:利用切片操作和工厂方法list方法拷贝就叫浅拷贝,只是拷贝了最外围的对象本身,内部的元素都只是拷贝了一个引用而已。

思路二:利用copy中的deepcopy方法进行拷贝就叫做深拷贝,外围和内部元素都进行了拷贝对象本身,而不是引用。

但是对于数字,字符串和其他原子类型对象等,没有被拷贝的说法,即便是用深拷贝,查看id的话也是一样的,如果对其重新赋值,也只是新创建一个对象,替换掉旧的而已。

转载请注明:快乐编程 » python中的深拷贝和浅拷贝理解

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

网友最新评论 (8)

  1. 简洁,清晰,美观
    victor2年前 (2015-12-03)回复
  2. 感谢解答!!!
    进藤HIKARU1年前 (2016-04-20)回复
  3. 因为名称是字符串,为不可变变量,修改会新建对象,所以只会影响对其修改的那个对象;但是‘age’和20是在列表中,列表是可变对象,修改时不会新建对象,所以影响了全部
    daveze1年前 (2016-06-10)回复
    • 陈杰斌
      是的哟。
      陈杰斌1年前 (2016-06-10)回复
      • 您好,关于这个问题的理解您可能是不太正确的。
        lpx1年前 (2016-07-26)回复
        • 陈杰斌
          具体是在哪个点上呢?
          陈杰斌1年前 (2016-07-26)回复
          • 是这样的,在我做实验的时候发现,即使是可变的元素修改(如int),也不会影响其他浅copy的list。
            假设有假设有list aa=[123,345,678],然后使用浅copy,复制一个bb,修改bb的第一个元素,aa并不受影响。
            虽然两个list的元素相同,但是id得到的结果不同,但又由于id每个元素得到的编号相同,所以我认为在list当中存在着一个索引的机制,类似于linux文件系统当中的inode,这个索引是在内存当中占有确切空间的,所以我们id一个list的时候现实的实际上是索引的内存地址。而list当中的元素修改的时候,无论这个元素是否会重新开辟一个空间,都会有一个新的指针指过去,所以只有被修改的list会改变,其他不影响。而list嵌套当中,外部的list的索引中指针指向的是内部的list的索引,而内部list的索引地址是不会改变的,所以,当内部的list元素发生变化时,所有的上层list都会发生变化。我觉得这个才是为什么浅copy修改外层元素时其他list不会发生变化,而修改内层时所有的都会变化的原因。
            lpx1年前 (2016-07-26)
          • 你想多了,"list aa=[123,345,678],然后使用浅copy,复制一个bb,修改bb的第一个元素,aa并不受影响",这里不存在引用拷贝,所以也就是值拷贝,所以aa和bb内存单元就不一样。一个变了,另一个当然没影响。
            BrownWong1年前 (2016-08-08)