Python 深浅拷贝

概念普及:对象、可变类型、引用

 

数据拷贝会涉及到Python中对象、可变类型、引用这3个概念,先来看看这几个概念,只有明白了他们才能更好的理解深拷贝与浅拷贝到底是怎么一回事。

 

Python对象

在Python中,对对象有一种很通俗的说法,万物皆对象。说的就是构造的任何数据类型都是一个对象,无论是数字,字符串,还是函数,甚至是模块,Python都对当做对象处理。

 

所有Python对象都拥有三个属性:身份、类型、值。

 


可变与不可变对象

在Python中,按更新对象的方式,可以将对象分为2大类:可变对象与不可变对象。

 可变对象:  列表、字典、集合

  所谓可变是指可变对象的值可变,身份是不变的。

 不可变对象:数字、字符串、元组

  不可变对象就是对象的身份和值都不可变。新创建的对象被关联到原来的变量名,旧对象被丢弃,垃圾回收器会在适当的时机回收这些对象。


引用

在 Python 程序中,每个对象都会在内存中申请开辟一块空间来保存该对象,该对象在内存中所在位置的地址被称为引用。在开发程序时,所定义的变量名实际就对象的地址引用。

引用实际就是内存中的一个数字地址编号,在使用对象时,只要知道这个对象的地址,就可以操作这个对象,但是因为这个数字地址不方便在开发时使用和记忆,所以使用变量名的形式来代替对象的数字地址。 在 Python 中,变量就是地址的一种表示形式,并不开辟开辟存储空间。

就像 IP 地址,在访问网站时,实际都是通过 IP 地址来确定主机,而 IP 地址不方便记忆,所以使用域名来代替 IP 地址,在使用域名访问网站时,域名被解析成 IP 地址来使用。

通过一个例子来说明变量和变量指向的引用就是一个东西

In [11]: age = 18

In [12]: id(age)
Out[12]: 1730306752

In [13]: id(18)
Out[13]: 1730306752

 

逐步深入:引用赋值

 

上边已经明白,引用就是对象在内存中的数字地址编号,变量就是方便对引用的表示而出现的,变量指向的就是此引用。赋值的本质就是让多个变量同时引用同一个对象的地址。  那么在对数据修改时会发生什么问题呢?

 

  • 不可变对象的引用赋值

    对不可变对象赋值,实际就是在内存中开辟一片空间指向新的对象,原不可变对象不会被修改。

下面通过案例来理解一下:

 

a与b在内存中都是指向1的引用,所以a、b的引用是相同的

 

In [1]: a = 1

In [2]: b = a

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

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

 

现在再给a重新赋值,看看会发生什么变化?

 

从下面不难看出:当给a 赋新的对象时,将指向现在的引用,不在指向旧的对象引用。

 

In [1]: a = 1

In [2]: b = a

In [5]: a = 2

In [6]: id(a)
Out[6]: 1730306816

In [7]: id(b)
Out[7]: 1730306496

 

 

  • 可变对象的引用赋值

    可变对象保存的并不是真正的对象数据,而是对象的引用。当对可变对象进行赋值时,只是将可变对象中保存的引用指向了新的对象。

仍然通过一个实例来体会一下,可变对象引用赋值的过程。

 

当改变l1时,整个列表的引用会指新的对象,但是l1与l2都是指向保存的同一个列表的引用,所以引用地址不会变。

 

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

In [4]: l2 = l1

In [5]: id(l1)
Out[5]: 1916633584008

In [6]: id(l2)
Out[6]: 1916633584008

In [7]: l1[0] = 11

In [8]: id(l1)
Out[8]: 1916633584008

In [9]: id(l2)
Out[9]: 1916633584008

 

 

浅拷贝

 1 >>> will = ["Will", 28, ["Python", "C#", "JavaScript"]]
 2 >>> willer=will
 3 >>> id(will)
 4 47459592
 5 >>> id(willer)
 6 47459592
 7 >>> [id(ele) for ele in will]
 8 [50904304, 8791323210848, 47459784]
 9 >>> [id(ele) for ele in willer]
10 [50904304, 8791323210848, 47459784]
11 >>> will[0]='willer'
12 >>> will[2].append("CSS")
13 >>> will
14 ['willer', 28, ['Python', 'C#', 'JavaScript', 'CSS']]
15 >>> willer
16 ['willer', 28, ['Python', 'C#', 'JavaScript', 'CSS']]
17 >>> [id(ele) for ele in will]
18 [50500848, 8791323210848, 47459784]
19 >>> [id(ele) for ele in willer]
20 [50500848, 8791323210848, 47459784]

 

 

通过分析代码

  • 首先,依然创建了一个will变量,指向一个list类型的对象
  • 然后,通过copy模块里面的浅拷贝函数copy(),对will指向的对象进行浅拷贝,然后浅拷贝生成的新对象赋值给wilber变量
    • 浅拷贝会创建一个新的对象,这个例子中"wilber is not will"
    • 但是,对于对象中的元素,浅拷贝就只会使用原始元素的引用(内存地址),也就是说"wilber[i] is will[i]"
  • 当对will进行修改的时候
    • 由于list的第一个元素是不可变类型,所以will对应的list的第一个元素会使用一个新的对象39758496
    • 但是list的第三个元素是一个可变类型,修改操作不会产生新的对象,所以will的修改结果会相应的反应到wilber上

总结一下,当我们使用下面的操作的时候,会产生浅拷贝的效果:

 

  • 使用切片[:]操作
  • 使用工厂函数(如list/dir/set)
  • 使用copy模块中的copy()函数

 

 

深拷贝

 1 >>> import copy
 2 >>> will = ["Will", 28, ["Python", "C#", "JavaScript"]]
 3 >>> wilber = copy.deepcopy(will)
 4 >>> id(will)
 5 47327624
 6 >>> id(wilber)
 7 47328200
 8 >>> [id(ele) for ele in will]
 9 [52508272, 8791323210848, 47327432]
10 >>> [id(ele) for ele in wilber]
11 [52508272, 8791323210848, 47328008]
12 >>> will[0] = "Wilber"
13 >>> will[2].append("CSS")
14 >>> id(will)
15 47327624
16 >>> id(wilber)
17 47328200
18 >>> [id(ele) for ele in will]
19 [52558128, 8791323210848, 47327432]
20 >>> [id(ele) for ele in wilber]
21 [52508272, 8791323210848, 47328008]
22 >>> will
23 ['Wilber', 28, ['Python', 'C#', 'JavaScript', 'CSS']]
24 >>> wilber
25 ['Will', 28, ['Python', 'C#', 'JavaScript']]

 

通过对代码分析

  • 首先,同样使用一个will变量,指向一个list类型的对象
  • 然后,通过copy模块里面的深拷贝函数deepcopy(),对will指向的对象进行深拷贝,然后深拷贝生成的新对象赋值给wilber变量
    • 跟浅拷贝类似,深拷贝也会创建一个新的对象,这个例子中"wilber is not will"
    • 但是,对于对象中的元素,深拷贝都会重新生成一份(有特殊情况,下面会说明),而不是简单的使用原始元素的引用(内存地址)
      • 例子中will的第三个元素指向39737304,而wilber的第三个元素是一个全新的对象39773088,也就是说,"wilber[2] is not will[2]"
  • 当对will进行修改的时候
    • 由于list的第一个元素是不可变类型,所以will对应的list的第一个元素会使用一个新的对象39758496
    • 但是list的第三个元素是一个可不类型,修改操作不会产生新的对象,但是由于"wilber[2] is not will[2]",所以will的修改不会影响wilber

 

posted on 2019-08-19 18:29  Null_Bug  阅读(205)  评论(0编辑  收藏  举报

导航