python 对象引用、可变性、浅复制深复制

参考《流畅的python》第8章

变量是盒子

变量是附加在对象上的标注。
在这里插入图片描述

1. 每个变量都有标识、类型和值。

可以把标识理解为对象在内存中的地址。对象一旦创建,它的标识绝不会变。

2. is运算符 和 ==

is 运算符比较两个对象的标识;id() 函数返回对象标识的整数表示。对象 ID 的真正意义在不同的实现中有所不同。在 CPython 中,id() 返回对象的内存地址,但是在其他 Python 解释器中可能是别的值。关键是,ID 一定是唯一的数值标注,而且在对象的生命周期中绝不会变。

== 运算符比较两个对象的值(对象中保存的数据),而 is 比较对象的标识。

在变量和单例值之间比较时,应该使用 is。目前,最常使用 is
检查变量绑定的值是不是 None。下面是推荐的写法

x is None
x is not None

is 运算符比 == 速度快,因为 Python 不用寻找并调用特殊方法,而是直接比较两个整数 ID。

a == b 是语法糖,等同于a.__eq__(b)

继承自 object 的 __eq__ 方法比较两个对象的 ID,结
果与 is 一样。但是多数内置类型使用更有意义的方式覆盖了 __eq__方法,会考虑对象属性的值。

3. 元组的相对不可变性

元组与多数 Python 集合(列表、字典、集)一样,保存的是对象的引用。(str, bytes, array.array则是单一类型序列,保存的是连续内存中的数据本身。)
元组的不可变性其实是指 tuple 保存的引用不可变,即每个元素的id不可变,与所引用的对象是否变化无关。
可理解为,tuple是个保存常量指针的数组,每个元素都是个常量指针,其指向固定的内存地址。
元组是不可变的,但如果引用的元素是可变的,元素依然
可变。

t1 = (1, 2, [30, 40])
t2 = (1, 2, [30, 40])
t1 == t2 # True
# id(t1[-1]) == 4302515784	
t1[-1].appned(40)
# t1 变为 (1, 2, [30, 40, 50])
# id(t1[-1]) == 4302515784	#且第3个元素的id没有变
t1 == t2 # False,t1 t2值不想等了。
4. 浅复制 VS 深复制
4.1 浅复制

即复制了最外层容器,副本中的元素是原容器中元素的引用。浅复制容易操作,但是得到的结果可能并不是你想要的。
在 http://www.pythontutor.com 中演示以下代码的执行过程:

l1 = [3, [66, 55, 44], (7, 8, 9)]
l2 = list(l1)	#浅复制,同 l2 = l1[:]

l1.append(100)
l1[1].remove(55)

l2[1] += [33, 22]
l2[2] += (10, 11)

在这里插入图片描述
l1中每个元素是一个对象的引用。
在这里插入图片描述
执行浅复制,l2的元素是l1元素的引用的复制,即指向相同的引用对象。通过l1修改所第2个元素列表内容,会使得l2的内容发生同样的变化。

在这里插入图片描述倒数第2句,会直接修改所引用的列表的内容,l1和l2第2个元素还是都指向此列表。
最后一句,元组是不可变的,所以会创建一个新的元组对象,并幅值给l2的第3个元素,此后,l1和l2的第3个元素指向不同的对象。

4.2 深复制

副本与原对象不共享内部元素的引用。使用copy模块,copy.deepcopy()实现。copy.copy()则是浅复制。

5. 函数形参与实参的关系,默认参数值
def func(a=[]):
	'''将列表a修改,并返回'''
	a.append('end')
	return a

l1 = ['1', '2', '3']
l2 = func(l1)
# l1也被修改了, l1 == l2 == ['1', '2', '3', 'end']

#使用形参a的默认值:
l3 = func()
# l3 == ['end']
l4 = func()
# l4 == ['end', 'end']
l5 = func()
# l5 == ['end', 'end', 'end']
# 发现,默认参数值a = [],好像记住了之前的值,每次默认参数变成了记住的新的参数。

l6 = func([1])
# l6 == [1, 'end']
# 不使用默认值时,又恢复了正常。

print(func().__defaults__)
# ['end', 'end', 'end']

函数形参:是函数内的局部变量。但是,实参幅值给形参时,

变量是附加在对象上的标识

所以,形参只是实参对象的引用,和实参绑定到相同的对象。函数内部对形参修改,会影响外部的实参对象。

函数形参的默认值:若形参有默认值,默认值在定义函数时计算,即运行到def语句时。并且,默认值会存入函数的 __defaults__ 属性中。这也就是上面代码中,l4, l5, l6记住了之前结果的原因。
如果默认值是个可变对象(列表),且函数内会修改该默认形参,则__defaults__中会一直储存此修改,引起意外的行为。

函数的默认参数,一般使用不可变对象,如None,单例值。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值