Python基础【3】:Python中的深浅拷贝解析


深浅拷贝

在研究Python的深浅拷贝区别前需要先弄清楚以下的一些基础概念:

变量--引用--对象(可变对象,不可变对象)

切片(序列化对象)--拷贝(深拷贝,浅拷贝)

我是铺垫~

一、【变量--引用--对象】:

Python中一切皆对象,甚至连type本身都是type对象。

  1 >>> type(type(n1))
  2 <class 'type'>

Python中的变量不同于与Java/c/c++不同,它是对象的引用,是动态型变量,数据类型无须提前声明,变量类型是根据对象的类型动态变化。

例如运行n=9,Python内部先在内存中找块地址来存储9,n只是对这个对象的引用,或者说将指针指向这个对象。

这里的整数对象9包含了两层含义:

1、数值为9

2、一个头部信息,告诉Python这是个int对象【可理解为一个指向int类型的指针】


image

小结:

变量是一个系统表的元素,拥有指向对象的连接的空间;

对象是被分配的一块内存,用来存储值;

引用是在变量赋值时自动形成的变量到对象的指针。


再来看下面的例子:

  1 >>> a = 3
  2 >>> b = a
  3 >>> id(a);id(b)
  4 1627390480
  5 1627390480
  6 >>> del a
  7 >>> b
  8 3
   >>> a = 5
   >>> b
   3

imageimage

可以看出,a和b共享引用,指向同一个内存地址(其id完全相同),所以将变量a删除或者将a赋值为4,都对变量b的数值无影响。

上面的例子有一个局限,就是数值类型对象是不可改变的,如果换成列表和元组对象呢?

二、【可变对象--不可变对象】

Python中的可变对象:可以修改的对象,列表和字典。

Python中的不可变对象:一旦创建就不可以修改的对象,字符串、元组、整数。

  1 >>> L1 = [2,3,4]
  2 >>> L2 = L1
  3 >>> L2
  4 [2, 3, 4]
  5 >>> L1= [5,6]
  6 >>> L2
  7 [2, 3, 4]
  8 >>> L1  = [1,2,3]
  9 >>> L2 = L1
 10 >>> L1[0] = 99
 11 >>> L1;L2
 12 [99, 2, 3]
 13 [99, 2, 3]
 14 #因为对象本身变了

如此一来,可变对象的变量共享引用就可能会有问题了(原本对源变量的修改,一不小心影响了其他的变量引用)。所以针对这种情景,才有了深浅copy。

那么如果不想改变上面L2的值有两种作法:切片和copy模块。

  1 >>> L1
  2 [99, 2, 3]
  3 >>> L2 = L1[:]
  4 >>> L1,L2
  5 ([99, 2, 3], [99, 2, 3])
  6 >>> id(L1);id(L2)
  7 3089554433480
  8 3089554433992
  9 >>> L1[0] = 100
 10 >>> L1;L2
 11 [100, 2, 3]
 12 [99, 2, 3]

拷贝

切片技术的应用范围:序列对象,即列表、字符串、元组。

注:序列是一类基本数据类型(字符串/列表/元组)的统称,这些数据类型都含有一些共同的特性。

切片不能应用于字典,对于字典只有dic.copy()方法和dic.deepcopy()方法。

  1 >>> dic = {'name':'bolen','age':18}
  2 >>> [item for item in dir(dic) if not item.startswith('__')]
  3 ['clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']
  4 
  5 #上面的语法和三元运算一样,x = 1 if n >2 else 3(或x = [2,1][n>2]),都是Python中的语法糖。
  1 >>> import copy
  2 >>> L3 = copy.copy(L1)
  3 >>> L4 = copy.deepcopy(L1)
  4 >>>

上面铺垫了这么多,就是想说深浅拷贝,既可应用于序列对象也可用于字典。

【浅拷贝】

浅拷贝只拷贝顶层对象或者说父级对象。

【深拷贝】

深拷贝是拷贝所有对象,包括顶级对象和镶套对象,或者说拷贝所有父对象和子对象。

闲言少叙,show code:

  1 >>> n1
  2 {'k1': 'lilei', 'k3': ['yy', 456], 'k2': 123}
  3 >>> n4 = n1
  4 >>> n2= copy.copy(n1)
  5 >>> n3 = copy.deepcopy(n1)
  6 >>> n1;n2;n3;n4
  7 {'k1': 'bboo', 'k3': ['yy', 456], 'k2': 123}
  8 {'k1': 'bboo', 'k3': ['yy', 456], 'k2': 123}
  9 {'k1': 'bboo', 'k3': ['yy', 456], 'k2': 123}
 10 {'k1': 'bboo', 'k3': ['yy', 456], 'k2': 123}
 11 >>> id(n1);id(n2);id(n3);id(n4)
 12 3089553992072
 13 3089554432904
 14 3089554359368
 15 3089553992072
 16 #深浅拷贝内存地址和源对象都不一样。
 17 >>> n1['k1'] = 'bolen'
 18 >>> n1;n2;n3;n4
 19 {'k1': 'bolen', 'k3': ['yy', 456], 'k2': 123}
 20 {'k1': 'bboo', 'k3': ['yy', 456], 'k2': 123}
 21 {'k1': 'bboo', 'k3': ['yy', 456], 'k2': 123}
 22 {'k1': 'bolen', 'k3': ['yy', 456], 'k2': 123}
 23 >>> n1['k3'][1]=789
 24 >>> n1;n2;n3;n4
 25 {'k1': 'bolen', 'k3': ['yy', 789], 'k2': 123}
 26 {'k1': 'bboo', 'k3': ['yy', 789], 'k2': 123}
 27 {'k1': 'bboo', 'k3': ['yy', 456], 'k2': 123}
 28 {'k1': 'bolen', 'k3': ['yy', 789], 'k2': 123}
 29 #当有多层镶套时,源对象改变后,浅拷贝和共享引用的值都变了,而深拷贝没有变。


image

【总结】

深浅拷贝都是对源对象的复制,占用不同的内存空间
如果源对象只有一级目录的话,源做任何改动,不影响深浅拷贝对象
如果源对象不止一级目录的话,源做任何改动,都要影响浅拷贝,但不影响深拷贝
序列对象的切片其实是浅拷贝,即只拷贝顶级的对象

原文地址:https://www.cnblogs.com/bolenzhang/p/6965834.html