【Python笔记】变量赋值传递时的引用和拷贝

变量、引用、对象

a = 0

上述语句中,创建了一个对象,这个对象是一个值,该值为0;再创建了一个变量a,变量a引用了对象。也可以理解成a是变量,0是对象,a引用了0。
在这里插入图片描述
变量是没有类型的,它可以指向任何类型的对象,在使用变量前必须先赋值,也就是说变量在被赋值时才创建的。可以看作是对象的一个临时标签。

对象是有类型的,其本质是分配的一块内存,存放着值。
对象有三要素:唯一标识(Id)、类型标识(Type)、值(Value)。
对象的两个标准的头部信息:类型标识符、引用计数器。

  • Id 用于唯一标识对象,也就是计算机内存中的地址,id(obj)返回对象唯一标识
  • Type 表示对象存储的数据的类型,type(obj)返回对象所属类型
  • Value 表示对象存储的数据的信息,print(obj)打印值
  • 引用计数器 用于记录当前对象的引用的数目,当它的值为0时,对象就会被回收

引用是一种关系,以内存中的指针的形式实现。

代码例子:

a = 0
print(id(a))
print(type(a))
print(a)

运行结果:

2157821427920
<class ‘int’>
0

赋值

赋值实际上可以理解为给对象取别名。赋值有两种情况:可变对象(列表、字典、集合)赋值 和 不可变对象(数值、字符串、元组)赋值。

可变对象赋值

代码

a = [0,1,2,3,4]
b = a
print(f"修改前,a和b的值:a = {a},b = {b}")
print(f"修改前,a和b的地址:a = {id(a)},b = {id(b)}")
b[0] = 5
print(f"修改 b[0] 后,a和b的值:a = {a},b = {b}")
print(f"修改 b[0] 后,a和b的地址:a = {id(a)},b = {id(b)}")
a[0] = 6
print(f"修改 a[0] 后,a和b的值:a = {a},b = {b}")
print(f"修改 a[0] 后,a和b的地址:a = {id(a)},b = {id(b)}")

运行结果

修改前,a和b的值:a = [0, 1, 2, 3, 4],b = [0, 1, 2, 3, 4]
修改前,a和b的地址:a = 2769219675904,b = 2769219675904
修改 b[0] 后,a和b的值:a = [5, 1, 2, 3, 4],b = [5, 1, 2, 3, 4]
修改 b[0] 后,a和b的地址:a = 2769219675904,b = 2769219675904
修改 a[0] 后,a和b的值:a = [6, 1, 2, 3, 4],b = [6, 1, 2, 3, 4]
修改 a[0] 后,a和b的地址:a = 2769219675904,b = 2769219675904

结论
从上述结果中看出,列表a赋值给列表b后,a和b指向同一个对象,即同一个内存空间,改变a、b其中一个的值,另一方也会跟着改变;同时值修改前后,a和b始终指向同一个内存。

不可变对象赋值

代码

a = 0
b = a
print(f"修改前,a和b的值:a = {a},b = {b}")
print(f"修改前,a和b的地址:a = {id(a)},b = {id(b)}")
b = 5
print(f"修改b后,a和b的值:a = {a},b = {b}")
print(f"修改b后,a和b的地址:a = {id(a)},b = {id(b)}")
a = 6
print(f"修改a后,a和b的值:a = {a},b = {b}")
print(f"修改a后,a和b的地址:a = {id(a)},b = {id(b)}")

运行结果

修改前,a和b的值:a = 0,b = 0
修改前,a和b的地址:a = 2913954955472,b = 2913954955472
修改b后,a和b的值:a = 0,b = 5
修改b后,a和b的地址:a = 2913954955472,b = 2913954955632
修改a后,a和b的值:a = 6,b = 5
修改a后,a和b的地址:a = 2913954955664,b = 2913954955632

结论
从上述结果中看出,a赋值给b后,a和b指向同一个对象,即同一个内存空间,修改b值,a值不会改变,同时b的地址会改变;同理,修改a值,b值不会改变,同时a的地址也会发生变化。
在这里插入图片描述

拷贝

拷贝是将旧变量引用的对象复制,新变量引用复制出来的新对象,也就是说拷贝可以创建出一个新的对象,新对象的值和旧对象是一致的。拷贝分为浅拷贝和深拷贝。

浅拷贝

浅拷贝有字典的copy()方法和分片表达式L[:],也有copy模块中copy.copy(a)方法。

先看例子第一个例子:

lst = [0, 1, 2, 3, 4]
lst_copy = lst.copy()
dic = {'a':1,'b':2}
dic_copy = dic.copy()
print("修改前a、b、c的地址值和值:")
print(f"lst的地址:{id(lst)},a = {lst}")
print(f"lst_copy的地址:{id(lst_copy)},a = {lst_copy}")
print(f"dic的地址:{id(dic)},a = {dic}")
print(f"dic_copy的地址:{id(dic_copy)},a = {dic_copy}")
print('=======================================')
lst[0] = 11
lst_copy[1] = 22
dic['c'] = 3
dic_copy['d'] = 4
print("修改后a、b、c的地址值和值:")
print(f"lst的地址:{id(lst)},a = {lst}")
print(f"lst_copy的地址:{id(lst_copy)},a = {lst_copy}")
print(f"dic的地址:{id(dic)},a = {dic}")
print(f"dic_copy的地址:{id(dic_copy)},a = {dic_copy}")

运行结果:

修改前a、b、c的地址值和值:
lst的地址:1818901275968,a = [0, 1, 2, 3, 4]
lst_copy的地址:1818901282304,a = [0, 1, 2, 3, 4]
dic的地址:1818625374080,a = {‘a’: 1, ‘b’: 2}
dic_copy的地址:1818625374336,a = {‘a’: 1, ‘b’: 2}
=======================================
修改后a、b、c的地址值和值:
lst的地址:1818901275968,a = [11, 1, 2, 3, 4]
lst_copy的地址:1818901282304,a = [0, 22, 2, 3, 4]
dic的地址:1818625374080,a = {‘a’: 1, ‘b’: 2, ‘c’: 3}
dic_copy的地址:1818625374336,a = {‘a’: 1, ‘b’: 2, ‘d’: 4}

从上面的运行结果看,浅拷贝后是分配一个新的内存,把旧对象的内容复制到新分配的内存中,即生成一个新对象,两个变量修改不会相互影响。

下面再看第二个例子:

import copy
dic = {'lst':[0,1,2,3,4],'int':0}
dic_copy = copy.copy(dic)
print('修改前:')
print(f'dic的值为{dic},地址为{id(dic)}')
print(f'dic_copy的值为{dic_copy},地址为{id(dic_copy)}')
print('=================================')
dic['str'] = 'abcde'
dic_copy['float'] = 2.5
print('往字典添加元素后:')
print(f'dic的值为{dic},地址为{id(dic)}')
print(f'dic_copy的值为{dic_copy},地址为{id(dic_copy)}')
print('=================================')
dic['lst'].append(555)
dic_copy['lst'].append(666)
print('分别修改键为“lst”的值后:')
print(f'dic的值为{dic},地址为{id(dic)}')
print(f'dic_copy的值为{dic_copy},地址为{id(dic_copy)}')
print('=================================')
dic['int'] = 1
dic_copy['int'] = 2
print('分别修改键为“int”的值后:')
print(f'dic的值为{dic},地址为{id(dic)}')
print(f'dic_copy的值为{dic_copy},地址为{id(dic_copy)}')
print('=================================')
print("dic['lst']和dic_copy['lst']的地址分别为:")
print(f"{id(dic['lst'])}, {id(dic_copy['lst'])}")

运行结果:

修改前:
dic的值为{‘lst’: [0, 1, 2, 3, 4], ‘int’: 0},地址为2246132692864
dic_copy的值为{‘lst’: [0, 1, 2, 3, 4], ‘int’: 0},地址为2246132693120
=================================
往字典添加元素后:
dic的值为{‘lst’: [0, 1, 2, 3, 4], ‘int’: 0, ‘str’: ‘abcde’},地址为2246132692864
dic_copy的值为{‘lst’: [0, 1, 2, 3, 4], ‘int’: 0, ‘float’: 2.5},地址为2246132693120
=================================
分别修改键为“lst”的值后:
dic的值为{‘lst’: [0, 1, 2, 3, 4, 555, 666], ‘int’: 0, ‘str’: ‘abcde’},地址为2246132692864
dic_copy的值为{‘lst’: [0, 1, 2, 3, 4, 555, 666], ‘int’: 0, ‘float’: 2.5},地址为2246132693120
=================================
分别修改键为“int”的值后:
dic的值为{‘lst’: [0, 1, 2, 3, 4, 555, 666], ‘int’: 1, ‘str’: ‘abcde’},地址为2246132692864
dic_copy的值为{‘lst’: [0, 1, 2, 3, 4, 555, 666], ‘int’: 2, ‘float’: 2.5},地址为2246132693120
=================================
dic[‘lst’]和dic_copy[‘lst’]的地址分别为:
2244261620096, 2244261620096

从上述结果看出,经过浅复制后的字典,字典地址不一样,但是在修改字典里列表的值时,它们相互影响,字典里可变对象的地址是一样的。

综上两个例子可以看出,浅拷贝只能复制对象本身,如果对象中引用了其他对象,那么被引用的对象是不能被复制的,也就是说浅复制只能复制父对象,父对象中的子对象是不可以复制的。

深拷贝

copy模块中的深拷贝方法是copy.deepcopy(x)
对于一些简单的对象而言,深拷贝和浅拷贝是一样的,如果需要复制一个既有不可变对象,又有可变对象的复杂对象时,需要用深拷贝。深拷贝会完全复制原变量相关的所有数据,在内存中生成一套完全一样的内容,无论怎么修改都不会相互影响。

import copy
dic = {'lst':[0,1,2,3,4],'int':0}
dic_copy = copy.deepcopy(dic)
print('修改前:')
print(f'dic的值为{dic},地址为{id(dic)}')
print(f'dic_copy的值为{dic_copy},地址为{id(dic_copy)}')
print('=================================')
dic['str'] = 'abcde'
dic_copy['float'] = 2.5
print('往字典添加元素后:')
print(f'dic的值为{dic},地址为{id(dic)}')
print(f'dic_copy的值为{dic_copy},地址为{id(dic_copy)}')
print('=================================')
dic['lst'].append(555)
dic_copy['lst'].append(666)
print('分别修改键为“lst”的值后:')
print(f'dic的值为{dic},地址为{id(dic)}')
print(f'dic_copy的值为{dic_copy},地址为{id(dic_copy)}')
print('=================================')
dic['int'] = 1
dic_copy['int'] = 2
print('分别修改键为“int”的值后:')
print(f'dic的值为{dic},地址为{id(dic)}')
print(f'dic_copy的值为{dic_copy},地址为{id(dic_copy)}')
print('=================================')
print("dic['lst']和dic_copy['lst']的地址分别为:")
print(f"{id(dic['lst'])}, {id(dic_copy['lst'])}")

运行结果:

修改前:
dic的值为{‘lst’: [0, 1, 2, 3, 4], ‘int’: 0},地址为2171635693440
dic_copy的值为{‘lst’: [0, 1, 2, 3, 4], ‘int’: 0},地址为2171910224192
=================================
往字典添加元素后:
dic的值为{‘lst’: [0, 1, 2, 3, 4], ‘int’: 0, ‘str’: ‘abcde’},地址为2171635693440
dic_copy的值为{‘lst’: [0, 1, 2, 3, 4], ‘int’: 0, ‘float’: 2.5},地址为2171910224192
=================================
分别修改键为“lst”的值后:
dic的值为{‘lst’: [0, 1, 2, 3, 4, 555], ‘int’: 0, ‘str’: ‘abcde’},地址为2171635693440
dic_copy的值为{‘lst’: [0, 1, 2, 3, 4, 666], ‘int’: 0, ‘float’: 2.5},地址为2171910224192
=================================
分别修改键为“int”的值后:
dic的值为{‘lst’: [0, 1, 2, 3, 4, 555], ‘int’: 1, ‘str’: ‘abcde’},地址为2171635693440
dic_copy的值为{‘lst’: [0, 1, 2, 3, 4, 666], ‘int’: 2, ‘float’: 2.5},地址为2171910224192
=================================
dic[‘lst’]和dic_copy[‘lst’]的地址分别为:
2171911857984, 2171911857088

在运行结果可以看出,深拷贝是完全复制旧对象,复制父对象的同时,递归复制所有子对象,新对象和旧对象的内存地址不一样。

总结

Python的赋值,一般情况下,对于不可变对象而言,可以理解为拷贝;对于可变对象而言,就是引用对象。(注: 对静态变量首次传递时也是引用,当需要修改静态变量时,因为静态变量不能改变,所以需要生成一个新的空间存储数据)
Python的拷贝,分为浅拷贝和深拷贝。对于简单对象(即对象里只有不可变对象,没有引用其他可变对象),深浅拷贝都一样;对于对象里引用了其他对象的,既有可变对象,又有不可变对象的,浅拷贝只能拷贝父对象,不能拷贝子对象,深拷贝是可以将父子对象都拷贝。

参考链接一
参考链接二
参考链接三

如有错漏之处,请批评指正,谢谢!

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值