Python的变量赋值,可变对象与不可变对象,浅复制(copy)与深复制(deepcopy)

一、变量赋值

Python 赋值关键要理解变量与对象的关系。 在Python中,变量是没有类型的,只有对象才有类型,变量赋值与对象赋值是不同的概念,变量赋值是用一个变量给另一个变量赋值,变量赋值的本质始终是增加同一对象的变量“标签”

# 对象赋值
>>> a=1
>>> b=1
>>> print(id(a) , id(b))
1470554896 1470554896    #当需要一个新的不可变对象时,会返回已经存在的某个类型和值都一致的对象
>>> alist=[1,2,3]
>>> blist=[1,2,3]
>>>print(id(alist) , id(blist))
44533400 44329760    #当需要一个新的可变对象时,会返回新的对象,子对象则需继续判断(不)可变对象

# 变量赋值
>>> print(id(a) , id(b))
>>> a , alist = 7 , [1, 2, 3]

>>> b = a ; blist = alist  #变量赋值

#改变标签,对象不变
>>> '{} address is {} and {} address is {}'.format(a , id(a) , b , id(b))
'7 address is 4549432928 and 7 address is 4549432928'
>>> '{} address is {} and {} address is {}'.format(alist , id(alist) , blist , id(blist))
'[1, 2, 3] address is 4597667336 and [1, 2, 3] address is 4597667336'

二、可变对象 和 不可变对象

可变对象和不可变对象的区别就是对象是否有容器,没有容器的对象就是对象自身,即“简单对象”,具备容器收纳子对象的对象就是“复杂对象”,“复杂对象”的子对象可以是“简单对象”也可以是“复杂对象”

♦️ 如list列表对象具有容器,list列表对象存储的内存地址就是容器的地址,作为容器内元素的子对象的改变不会影响到容器的改变,所以list列表对象属于可变对象
♦️ 而如int整数对象就不具有容器,对象就是其自身,对象发生改变因为对象本身无法进行修改,实际是另外新建对象变更指针,所以int整数对象属于不可变对象
♦️ 特别注意,Python规定tuple元组虽然有外层容器,但依然属于不可变对象

再次强调,变量赋值后对象不发生改变则对象只是增加变量“标签”,但变量赋值后如果对象发生改变,将由于python分为 可变对象不可变对象会导致不同的对象复制行为变量指向行为

对于可变对象,可变是指通过变量对作为容器内元素的子对象进行修改,修改行为不会改变容器的存储地址,容器没有发生复制行为,也没有发生开辟新的内存地址行为,因此修改前后变量指向的是同一对象(容器存储的地址)
而子对象的复制行为则取决于发生修改的子对象是否也具有容器,是可变对象还是不可变对象,如果还有子对象的子对象则继续层层判断是否具有容器

python可变对象有 list列表、dict字典、set集合

而对于不可变对象,不可变是指没有容器的对象自身是不可以被改变的,会在原对象不变的情况下直接另外新建更改值的对象并存储在新开辟的内存地址再指向原变量,因此修改前后变量指向的是不同对象(新旧对象存储的地址)

python 不可变对象有 int整数、float浮点数、str字符串、tuple元组、None空

□ 可变对象

>>>alist=[1,2,3]

>>>blist=alist  #指向同一个对象[1,2,3]

>>>alist.append(4)  #可变对象可以原址修改

>>>'{} address is {}'.format(alist,id(alist))  #指向原对象的地址
'[1, 2, 3, 4] address is 4505851912'

>>>'{} address is {}'.format(blist,id(blist)) #指向原对象的地址
'[1, 2, 3, 4] address is 4505851912'

□ 不可变对象

>>>a=7

>>>b=a  #指向同一个对象7

>>>a=8  #不可变对象不能原址修改,换址存储

>>>'{} address is {}'.format(a,id(a))  #指向新对象的地址
'8 address is 4457268864'

>>>'{} address is {}'.format(b,id(b))  #指向原对象的地址
'7 address is 4457268832'

三、深复制 和 浅复制

在说明可变对象和不可变对象的区别时提到过,复杂对象(即可变对象)就是具有容器包含子对象(子对象可以是简单对象也可以是复杂对象)的对象,如列表作为复杂对象就包含了作为列表元素的子对象(子对象可以是字符串的简单对象也可以是字典的复杂对象),浅复制和深复制都是针对具有容器的复杂对象而言的。而对于数字、字符串以及其它简单对象(即不可变对象),由于不具有容器,对象本身就是直接独立存储的,因此没有复制一说,对简单对象的复制实际就是变量赋值,仅仅是对象的引用

♦️ 深复制

所谓“深复制”,是指创建一个新变量指向新复制对象容器和子对象的完整复制

深拷贝只有一种方式:copy模块中的deepcopy函数

import copy

>>> alist = [1, 2, 3]

>>> blist = copy.deepcopy(alist)

>>> print(id(alist), id(blist))  # alist和blist的身份不同
4393552840 4393544264

>>> print(id(alist[0]) , id(blist[0]))  # 但为什么alist和blist的子对象身份还是相同
4345324960 4345324960

为什么使用了深复制,alist和blist的子对象的id还是一样?

答:这就是上面提到过的复制是仅针对有容器的复杂对象而言,这里子对象 alist[0] 是简单对象的整数1,对于数字、字符串以及其它简单对象(即不可变对象),由于不具有容器,对象本身就是直接独立存储的,复制也仅仅是对象的引用

♦️ 浅复制

所谓“浅复制”,是指创建一个新变量指向新复制对象容器但不复制子对象的不完整复制

常见的浅复制有:切片操作、工厂函数、对象的copy()方法、copy模块中的copy函数

import copy

>>> alist = [1, 2, [3,4]]

>>> blist = copy.copy(alist)

# 原对象alist和浅复制blist的容器是不同的
>>> print(id(alist), id(blist))
4607363016 4604397384

# 原对象alist和浅复制blist的容器包含的子对象是同一个
>>> print(id(alist[0]) , id(blist[0]))
4557764000 4557764000    # 但子对象[0]由于是不可变对象,所以不能证明是由于浅复制子对象的特性还是简单对象的特性造成指向同一个对象
>>> print(id(alist[2]) , id)(blist[2]))
4607391368 4607391368    # 而子对象[2]由于是可变对象,所以可以证明确实是浅复制子对象的特性造成指向同一个对象

最后可以用一个例子来展示“浅复制”与“深复制”的区别:

import copy

>>> alist = [ 1 , 2 , [4, 6] , [7, 9] ]

>>> blist = copy.copy(alist)              # alist浅复制得到blist
>>> clist = copy.deepcopy(alist)          # alist深复制得到clist
>>> print(id(alist), id(blist) ,id(clist))           # 无论浅复制blist还是深复制clist,容器都会因为是新复制而不同
4606102536 4607235016 4607177736

# 子对象是不可变对象的复制
>>> id(alist[0])           # 原对象alist子对象[0]是不可变对象1
4557764000
>>> id(blist[0])           # 浅复制blist子对象与原对象alist的子对象是同一个
4557764000
>>> id(clist[0])           # 深复制clist的子对象是原对象alist的子对象的新复制,但1是作为不可变对象的复制就是引用
 4557764000

# 子对象是可变对象的复制
>>> id(alist[3])           # 原对象alist子对象[3]是可变对象[7,9]
4607027784
>>> id(blist[3])           # 浅复制blist子对象与原对象alist的子对象是同一个
4607027784
>>> id(clist[3])           # 深复制clist子对象是原对象alist的子对象的新复制
4607224200

♦️ 深浅复制的作用
1、减少内存的使用
2、在做数据的清洗、修改或者入库的时候,对原数据进行复制一份,以防数据修改之后,找不到原数据

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值