Python浅复制、深复制与直接赋值的理解与区分

    在Python编程时,我们需要使用比较复杂的数据结构或者对象,例如二维列表或者数组(因此,对于极为简单的情况本文不予考虑)。在函数调用或者数据处理的过程中,我们常常涉及到较复杂的数据结构的浅复制和深复制。深入地了解二者的运行逻辑和区别非常重要,能够让我们顺利实现预期的程序功能,规避许多奇奇怪怪的错误。

一、基本定义

在深入分析浅复制、深复制与直接赋值的异同之前,我们先要搞清楚几个python的基本概念。

  • 对象:python系统分配好的一块内存,用以存储特定的值,如数字、字符、字符串、元祖等。
  • 不可变对象:不可修改的对象,包括字符串、元组、数值类型。对象所指向的内存中的值不能被改变。当变量赋值时,由于其所指的值不能被改变,就得另行开辟一个新的内存空间用以存储新的值;然后,变量再指向这个新的地址。
  • 可变对象:可以修改的对象,包括列表、字典、集合。对象所指向的内存中的值可以被改变。变量内部的内容被赋值或改变时,无须开辟新的内存空间,其内部的值直接发生了改变。)
  • 变量:python系统表中的一个自定义元素,拥有指向对象的连接空间。变量有多种类型,包括可变的变量和不可变变量
  • 引用:从变量到对象的指针,或者索引。

在此基础之上,我们有如下基本认识:

1.赋   值: 用Python的“=”直接赋值,仅仅是增加了原对象的引用,不开辟新的内存空间(即两个变量的地址是相同的)。本质上,相当于原对象多一个新马甲!

2.浅复制:用copy模块的copy函数,创建了新的对象和存储空间,其内容仅是原对象的引用(而非复制)。本质上,浅复制得到的对象虽是一个新的对象,但与原对象之间仍有关联。

3.深复制:用copy模块的deepcopy函数,不仅创建了新的对象和存储空间,而且其内容也复制了原对象内部的所有元素,包括多层嵌套的元素。本质上,深复制得到的对象是一个全新的对象,与原对象之后再无关联。

因此,为避免那些说不清道不明的麻烦,可以遵循如下原则:

  • 为了完全避免对原始数据产生影响,可以使用深复制;
  • 为了根据需要改变原始数据的底层不可变对象,如数字、字符,可以使用浅复制;
  • 为了安全,尽量不要使用简单的赋值。

二、示例

    对于来个简单的示例,a为原始对象,一个二维列表,b是对a的直接赋值,c和d分别是a的浅复制和深复制。

首先,看看原始对象的改变对三种操作得到的变量的影响。

第一种情况:原列表对象整体赋值

import copy

a = [['biu', 'biu'], ['biu', 'biu'], ['biu', 'biu']]
b = a
c = copy.copy(a)
d = copy.deepcopy(a)
print("初始状态\n原值 :id(a)->>>", id(a), id(a[0][0]), a)
print("赋值 :id(b)->>>", id(b), id(b[0][0]), b)
print("浅复制:id(c)->>>", id(c), id(c[1][0]), c)
print("深复制:id(d)->>>", id(d), id(d[2][0]), d)

a = 'changed'
print("修改状态\n原值 :id(a)->>>", id(a), id(a[0][0]), a)
print("赋值 :id(b)->>>", id(b), id(b[0][0]), b)
print("浅复制:id(c)->>>", id(c), id(c[1][0]), c)
print("深复制:id(d)->>>", id(d), id(d[2][0]), d,'\n')

输出:
"C:\Program Files\Python38\python.exe" F:/Desktops/User/dd/test.py
初始状态
原值 :id(a)->>> 2567866112000 2567866037168 [['biu', 'biu'], ['biu', 'biu'], ['biu', 'biu']]
赋值 :id(b)->>> 2567866112000 2567866037168 [['biu', 'biu'], ['biu', 'biu'], ['biu', 'biu']]
浅复制:id(c)->>> 2567866112704 2567866037168 [['biu', 'biu'], ['biu', 'biu'], ['biu', 'biu']]
深复制:id(d)->>> 2567866112384 2567866037168 [['biu', 'biu'], ['biu', 'biu'], ['biu', 'biu']]
修改状态
原值 :id(a)->>> 2567866037552 2567865485232 changed
赋值 :id(b)->>> 2567866112000 2567866037168 [['biu', 'biu'], ['biu', 'biu'], ['biu', 'biu']]
浅复制:id(c)->>> 2567866112704 2567866037168 [['biu', 'biu'], ['biu', 'biu'], ['biu', 'biu']]
深复制:id(d)->>> 2567866112384 2567866037168 [['biu', 'biu'], ['biu', 'biu'], ['biu', 'biu']] 

可见,一次性地改变整个变量后,a的地址空间是重新开辟的。而b、c、d三者都不受影响,这是因为,它们或是与a的旧地址空间挂钩,或是与a无关。 

第二种情况:原列表对象内部的浅层可变对象的整体赋值

a[0] = 'changed'
print("修改状态\n原值 :id(a)->>>", id(a), id(a[0][0]), a)
print("赋值 :id(b)->>>", id(b), id(b[0][0]), b)
print("浅复制:id(c)->>>", id(c), id(c[1][0]), c)
print("深复制:id(d)->>>", id(d), id(d[2][0]), d,'\n')

输出:
初始状态
原值 :id(a)->>> 2088799852672 2088799777840 [['biu', 'biu'], ['biu', 'biu'], ['biu', 'biu']]
赋值 :id(b)->>> 2088799852672 2088799777840 [['biu', 'biu'], ['biu', 'biu'], ['biu', 'biu']]
浅复制:id(c)->>> 2088799853376 2088799777840 [['biu', 'biu'], ['biu', 'biu'], ['biu', 'biu']]
深复制:id(d)->>> 2088799853056 2088799777840 [['biu', 'biu'], ['biu', 'biu'], ['biu', 'biu']]
修改状态
原值 :id(a)->>> 2088799852672 2088768965744 ['changed', ['biu', 'biu'], ['biu', 'biu']]
赋值 :id(b)->>> 2088799852672 2088768965744 ['changed', ['biu', 'biu'], ['biu', 'biu']]
浅复制:id(c)->>> 2088799853376 2088799777840 [['biu', 'biu'], ['biu', 'biu'], ['biu', 'biu']]
深复制:id(d)->>> 2088799853056 2088799777840 [['biu', 'biu'], ['biu', 'biu'], ['biu', 'biu']] 

可见,对二维列表a内部的可变对象一维变量a[0]赋值后,a的地址空间是不变的,但a[0][0]的地址变了,说明重新开辟了内存空间。b由于是a另一件马甲,因而它与a完全相同;c不受影响是因为,它内部的可变对象c[0]是指向a[0]的旧地址的,因此也不受影响;d与a无关,所以,完全不受影响。 

第三种情况:原列表对象内部的深层可变对象的赋值

a[0][0] = 'changed'
print("修改状态\n原值 :id(a)->>>", id(a), id(a[0][0]), a)
print("赋值 :id(b)->>>", id(b), id(b[0][0]), b)
print("浅复制:id(c)->>>", id(c), id(c[1][0]), c)
print("深复制:id(d)->>>", id(d), id(d[2][0]), d,'\n')

输出:
初始状态
原值 :id(a)->>> 1825799690304 1825799615472 [['biu', 'biu'], ['biu', 'biu'], ['biu', 'biu']]
赋值 :id(b)->>> 1825799690304 1825799615472 [['biu', 'biu'], ['biu', 'biu'], ['biu', 'biu']]
浅复制:id(c)->>> 1825799691008 1825799615472 [['biu', 'biu'], ['biu', 'biu'], ['biu', 'biu']]
深复制:id(d)->>> 1825799690688 1825799615472 [['biu', 'biu'], ['biu', 'biu'], ['biu', 'biu']]
修改状态
原值 :id(a)->>> 1825799690304 1825799615856 [['changed', 'biu'], ['biu', 'biu'], ['biu', 'biu']]
赋值 :id(b)->>> 1825799690304 1825799615856 [['changed', 'biu'], ['biu', 'biu'], ['biu', 'biu']]
浅复制:id(c)->>> 1825799691008 1825799615472 [['changed', 'biu'], ['biu', 'biu'], ['biu', 'biu']]
深复制:id(d)->>> 1825799690688 1825799615472 [['biu', 'biu'], ['biu', 'biu'], ['biu', 'biu']] 

可见,对二维列表a内最底层的不可变对象a[0][0]赋值后,a和a[0]的地址空间都是不变的。b由于是a另一件马甲,因而它与a完全相同;c这次受到了影响,这是因为,它内部的可变对象c[0]是指向a[0]的旧地址的,因此自然也受到了影响;d与a无关,所以,完全不受影响。 

其次,再看看新对象被改变时对原始对象的影响。

赋值:改变直接赋值的变量(对象)内部的不可变对象

b[0][0] = 'BB'
print("修改状态\n原值 :id(a)->>>", id(a), id(a[0][0]), a)
print("赋值 :id(b)->>>", id(b), id(b[0][0]), b)
print("浅复制:id(c)->>>", id(c), id(c[1][0]), c)
print("深复制:id(d)->>>", id(d), id(d[2][0]), d,'\n')

输出:
初始状态
原值 :id(a)->>> 2995689904192 2995689829360 [['biu', 'biu'], ['biu', 'biu'], ['biu', 'biu']]
赋值 :id(b)->>> 2995689904192 2995689829360 [['biu', 'biu'], ['biu', 'biu'], ['biu', 'biu']]
浅复制:id(c)->>> 2995689904896 2995689829360 [['biu', 'biu'], ['biu', 'biu'], ['biu', 'biu']]
深复制:id(d)->>> 2995689904576 2995689829360 [['biu', 'biu'], ['biu', 'biu'], ['biu', 'biu']]
修改状态
原值 :id(a)->>> 2995689904192 2995689829744 [['BB', 'biu'], ['biu', 'biu'], ['biu', 'biu']]
赋值 :id(b)->>> 2995689904192 2995689829744 [['BB', 'biu'], ['biu', 'biu'], ['biu', 'biu']]
浅复制:id(c)->>> 2995689904896 2995689829360 [['BB', 'biu'], ['biu', 'biu'], ['biu', 'biu']]
深复制:id(d)->>> 2995689904576 2995689829360 [['biu', 'biu'], ['biu', 'biu'], ['biu', 'biu']] 

可见,修改直接赋值的对象b中的底层不可变对象,会导致a和c同步变化;d与a无关,所以,自然也完全不受影响。 

浅复制:改变浅复制的变量(对象)内部的不可变对象

c[1][0] = 'BB'
print("修改状态\n原值 :id(a)->>>", id(a), id(a[0][0]), a)
print("赋值 :id(b)->>>", id(b), id(b[0][0]), b)
print("浅复制:id(c)->>>", id(c), id(c[1][0]), c)
print("深复制:id(d)->>>", id(d), id(d[2][0]), d,'\n')

输出:
初始状态
原值 :id(a)->>> 2729161021632 2729160946800 [['biu', 'biu'], ['biu', 'biu'], ['biu', 'biu']]
赋值 :id(b)->>> 2729161021632 2729160946800 [['biu', 'biu'], ['biu', 'biu'], ['biu', 'biu']]
浅复制:id(c)->>> 2729161022336 2729160946800 [['biu', 'biu'], ['biu', 'biu'], ['biu', 'biu']]
深复制:id(d)->>> 2729161022016 2729160946800 [['biu', 'biu'], ['biu', 'biu'], ['biu', 'biu']]
修改状态
原值 :id(a)->>> 2729161021632 2729160946800 [['biu', 'biu'], ['BB', 'biu'], ['biu', 'biu']]
赋值 :id(b)->>> 2729161021632 2729160946800 [['biu', 'biu'], ['BB', 'biu'], ['biu', 'biu']]
浅复制:id(c)->>> 2729161022336 2729160947184 [['biu', 'biu'], ['BB', 'biu'], ['biu', 'biu']]
深复制:id(d)->>> 2729161022016 2729160946800 [['biu', 'biu'], ['biu', 'biu'], ['biu', 'biu']] 

可见,修改浅复制对象c中的底层不可变对象,会导致a和b同步变化,这是因为c[1][0]是指向直接a[1][0]的;d与a无关,所以,自然也完全不受影响。 

 深复制:改变深复制的变量(对象)内部的不可变对象

d[2][0] = 'BB'
print("修改状态\n原值 :id(a)->>>", id(a), id(a[0][0]), a)
print("赋值 :id(b)->>>", id(b), id(b[0][0]), b)
print("浅复制:id(c)->>>", id(c), id(c[1][0]), c)
print("深复制:id(d)->>>", id(d), id(d[2][0]), d,'\n')

输出:
初始状态
原值 :id(a)->>> 1795471792256 1795471717488 [['biu', 'biu'], ['biu', 'biu'], ['biu', 'biu']]
赋值 :id(b)->>> 1795471792256 1795471717488 [['biu', 'biu'], ['biu', 'biu'], ['biu', 'biu']]
浅复制:id(c)->>> 1795471792960 1795471717488 [['biu', 'biu'], ['biu', 'biu'], ['biu', 'biu']]
深复制:id(d)->>> 1795471792640 1795471717488 [['biu', 'biu'], ['biu', 'biu'], ['biu', 'biu']]
修改状态
原值 :id(a)->>> 1795471792256 1795471717488 [['biu', 'biu'], ['biu', 'biu'], ['biu', 'biu']]
赋值 :id(b)->>> 1795471792256 1795471717488 [['biu', 'biu'], ['biu', 'biu'], ['biu', 'biu']]
浅复制:id(c)->>> 1795471792960 1795471717488 [['biu', 'biu'], ['biu', 'biu'], ['biu', 'biu']]
深复制:id(d)->>> 1795471792640 1795471717872 [['biu', 'biu'], ['biu', 'biu'], ['BB', 'biu']] 

可见,修改深复制对象d中的底层不可变对象,不会导致a、b、c同步变化,这是因为d与a无关!

  • 12
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值