9

浅显直白的Python深拷贝与浅拷贝区别说明 - 菜鸟阿乔

 1 year ago
source link: https://www.cnblogs.com/youqiaohaozi/p/17135116.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

浅显直白的Python深拷贝与浅拷贝区别说明

一、可变数据类型与不可变数据类型

  在开始说深拷贝与浅拷贝前,我们先来弄清楚,可变对象与不可变对象

  总的来说,Python数据类型可分为可变数据类型与不可变数据类型

  可变数据类型:在不改变对象所指向的地址的前提下,地址中的值是可以改变的,例如列表[1, 2, 3],我们可以改为[2,3]并不需要变更它指向的地址。列表、字典、集合都是可变数据类型

  不可变数据类型:在不改变对象所指向的地址的前提下,地址中的值是不可变的,所以如果修改了对象的值,就相当于在另一个新的地址,存储了新的值。Python中元组、字符串、数值、布尔值都是不可变数据类型。

二、Python深拷贝与浅拷贝的区别

  在弄清楚了可变对象和不可变对象之后,我们进入正题,看下Python的深拷贝与浅拷贝的区别

  1. 浅拷贝:

   仅拷贝父对象,可理解为仅拷贝对象第一层。浅拷贝之后,新旧对象本身指向的地址不同了,但子对象指向的地址仍然相同,我们可以用copy.copy()和可变数据类型的切片来进行浅拷贝

m = [1, 0, [2, 3, 4, [5, 6]]]
n = m.copy()
p = m[:]
print(f'对象m的地址是{id(m)},对象n的地址是{id(n)},对象p的地址是{id(p)}')
print(f'm[0]的地址是{id(m[0])},n[0]的地址是{id(n[0])},p[0]的地址是{id(p[0])}')
print(f'm[2]的地址是{id(m[2])},n[2]的地址是{id(n[2])},p[2]的地址是{id(p[2])}')
输出:

对象m的地址是1322908811144,对象n的地址是1322908811080,对象p的地址是1322908763400
m[0]的地址是140727539432512,n[0]的地址是140727539432512,p[0]的地址是140727539432512
m[2]的地址是1322908811208,n[2]的地址是1322908811208,p[2]的地址是1322908811208

打印结果可以看到,浅拷贝之后,新对象n, p的地址与m不同,但n, p的子对象地址与m中子对象地址是相同的

    此时,我们对新对象的子对象进行修改,我们来修改一下n[0]看一下结果

n[0] =99
print(f' m[0]={m[0]}\n n[0]={n[0]}\n p[0]={p[0]}')
print(f' m[0]的地址:{id(m[0])}\n n[0]的地址:{id(n[0])}\n p[0]的地址:{id(p[0])}')

输出:
 m[0]=1
 n[0]=99
 p[0]=1
 m[0]的地址:140727543364672
 n[0]的地址:140727543367808
 p[0]的地址:140727543364672

    可以看到,n[0]的地址和值都变了, m[0]和p[0]并没有变,是为什么呢?

    记得咱们最开始介绍了可变对象和不可变对象,这里的n[0]是数值,是不可变对象,所以在地址不改变的情况下,它的值是不变的;

    而我们在给它赋值时,相当于是把它指向了另一个地址,存储新值,而m[0]和p[0]指向的地址并没有变化

  接下来咱们再来尝试变更一下n[2]吧

print('----------------------------修改前----------------------------------\n '
      f'm[2]的地址:{id(m[2])}\n n[2]的地址:{id(n[2])}\n p[2]的地址:{id(p[2])}')

n[2][1] = 'n21'

print('----------------------------修改后----------------------------------\n'
      f' m[2]={m[2]}\n n[2]={n[2]}\n p[2]={p[2]}')
print(f' m[2]的地址:{id(m[2])}\n n[2]的地址:{id(n[2])}\n p[2]的地址:{id(p[2])}')


输出:
----------------------------修改前----------------------------------
 m[2]的地址:2235118923976
 n[2]的地址:2235118923976
 p[2]的地址:2235118923976
----------------------------修改后----------------------------------
 m[2]=[2, 'n21', 4, [5, 6]]
 n[2]=[2, 'n21', 4, [5, 6]]
 p[2]=[2, 'n21', 4, [5, 6]]
 m[2]的地址:2235118923976
 n[2]的地址:2235118923976
 p[2]的地址:2235118923976

    可以看到,变更前后n[2]、m[2]和p[2]的地址都是没变的, 原因你已经知道了吧,是的,因为n[2]是可变对象,是可以在地址中直接变更值的。

  2. 深拷贝

   深拷贝完全父对象与子对象。可使用copy模块的deepcopy()方法进行深拷贝,此外使用for循环复制可迭代序列也是深拷贝

m = [1, 0, [2, 3, 4, [5, 6]]]
n1 = copy.deepcopy(m)
print(f'对象m的地址是{id(m)},对象n1的地址是{id(n1)}')
print(f'm[0]的地址是{id(m[0])},n1[0]的地址是{id(n1[0])}')
print(f'm[2]的地址是{id(m[2])},n1[2]的地址是{id(n1[2])}')

输出:
对象m的地址是2551218893640,对象n1的地址是2551219173192
m[0]的地址是140727516494912,n1[0]的地址是140727516494912
m[2]的地址是2551218833480,n1[2]的地址是2551221308040

     打印结果可以看出,新旧对象本身的地址,和可变子对象地址都是不同的。

     这里看到m[0]和n1[0]的地址相同,但不可变对象的值变更地址就会变更,所以不会有问题。

  综上,咱们在实际应用中,如果拷贝对象的子对象都是不可变对象,那么使用浅拷贝和深拷贝都行,

  但如果待拷贝对象中有可变子对象,需要注意根据实际需求选择使用深拷贝还是浅拷贝。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK