tylderen +

Python 深浅拷贝

在Python里面,一切都是对象。Python变量区别于其他编程语言的申明&赋值方式,采用的是创建&指向的方式实现的。变量其实是对对象的引用,类似于c/c++里面的指针,指向对象的地址。 这也构成了Python动态语言的基础,即变量的类型可以在运行时根据它所指向的对象来确定。 赋值操作不会复制对象,只会生成对原对象的一个新的绑定。

In [1]: a = [1, 2, 3]

In [2]: b = a

In [3]: id(a)
Out[3]: 140578655111792

In [4]: id(b)
Out[4]: 140578655111792

这里可以看出a,b拥有相同的id,id是指对象在内存里面的地址,所以,换句话说,a,b指向的是同一个对象。 所以:

In [5]: b[2] = 5

In [6]: b
Out[6]: [1, 2, 5]

In [7]: a
Out[7]: [1, 2, 5]

看到这里,你可能会担心了:假如我只想对b操作,结果它偷偷地对a也进行了相同的操作,这样不就会带来很多隐患嘛?!

不用担心,Python提供了专门的处理方法。 Python的copy模块提供了两个接口:

copy.copy(x)
    Return a shallow copy of x.

copy.deepcopy(x)
    Return a deep copy of x.

copy和deepcopy的不同在于:在对一个复合对象(包含其他对象,像list,类实例等)进行复制时,copy会构造一个新的复合对象并尽可能地把原对象里面元素的引用复制进来,而deepcopy会构造一个新的复合对象并会把原对象里面的所有元素递归地复制进来,生成一个完完全全的新对象并保持内容与原对象完全相同。 看起来像是三个新对象:

In [1]: a = [1, 2, 3, [4,5]]

In [2]: import copy

In [3]: b = copy.copy(a)

In [4]: c = copy.deepcopy(a)

In [5]: id(a)
Out[5]: 140029127267824

In [6]: id(b)
Out[6]: 140029127212856

In [7]: id(c)
Out[7]: 140029127239224

但是:

In [8]: id(a[3])
Out[8]: 140029127267752

In [9]: id(b[3])
Out[9]: 140029127267752

In [10]: id(c[3])
Out[10]: 140029127239152

In [11]: a[3][1] = 6

In [12]: a
Out[12]: [1, 2, 3, [4, 6]]

In [13]: b
Out[13]: [1, 2, 3, [4, 6]]

In [14]: c
Out[14]: [1, 2, 3, [4, 5]]

所以,我们可以总结出:

  • 赋值操作是最浅层次的一种拷贝,称它为别名(alias)也许更合适;
  • 浅拷贝copy在拷贝复合对象时,会生成一个新的对象,但是里面的元素还是拷贝的引用;
  • 深拷贝deepcopy在拷贝复合对象时,生成了一个全新的对象,和原有的对象没有牵扯,对它的操作不会担心会对原有对象造成影响;

当然,有人对赋值操作的说法抱有怀疑,可以举个例子:

In [28]: a = 10000000000000000000000000

In [29]: id(a)
Out[29]: 139824434074808

In [30]: a += 1

In [31]: a
Out[31]: 10000000000000000000000001L

In [32]: id(a)
Out[32]: 139824434075488

这个单纯的加一操作:

a += 1 

好像有点跟我们想象的不大一样,在其他传统语言中,应该是先声明a这个变量,并在内存中为a开辟一块内存,将 10000000000000000000000000 存入a所在的内存,执行加一操作时将得到的 10000000000000000000000001L 再存入到a所在的内存地址; 但是在Python中,先在内存中创建了一个 10000000000000000000000000 的对象,然后将a指向了它。在执行加法操作的时候,实际上通过加法操作得到了一个 10000000000000000000000001L 的新对象,并将a指向这个新的对象。整个执行过程中,变化的是a指向的内存地址,上面的结果也印证了我们的说法。

Blog

Opinion

Project