古灵精怪的python——地址,浅拷贝与身份运算符

首先抛出一个问题,吸引读者的阅读兴趣(如果您觉得这个不是问题,那么这篇文章不适合您:)

请看如下代码:

>>>a=3>>>b=3>>>a==bTrue>>>a is bTrue>>>b=a# 这没问题>>>a=3>>>a==b# 这看起来也很合理True>>>a is bTrue>>>a is b>>>a=(2,3)>>>b=(2,3)>>>b=aFalse # ???why?>>>a==bTrue>>>a is bTrueTrue # ???why?>>>a==b

好了,整篇文章都是围绕这个问题展开的。长久以来我都习惯用is而不用==来进行两个对象的比较(python中一切皆对象) 直到今天出了一个bug后才了解到这两者之间的不同,挖到python的一个大坑之余,不禁出了一身冷汗。。。

is还是== ?

补充知识

id() 用于获取对象在内存中的地址,并以十进制展示出来。如:

>>>a=3>>>id(a)140602638349720>>>hex(id(a))# 还原成我们看着更顺眼的16进制,但是本文以10进制地址为主(因为懒)'0x7fe09a503598'

顾名思义,is是“相同”,而==是指两者之间的”相等“关系。所谓相同,比较的是两者之间的在内存中的位置,

>>>a=3>>>id(a)140602638349720>>>b=3# b指向的是和a指向的同一块地址(但是并不意味这改变了a,b也会相应改变)>>>id(b)140602638349720140602638349720>>>c=a # a的引用复制给c,在内存中其实是指向了用一个对象>>>id(c)>>>a is bTrueTrue>>>a is cTrue>>>b is c

我们看到,上面a,b,c的地址相同,所以他们互相之间”相同“

而相等则两者之间的数值对应相等

>>>a=3>>>b=a>>>b>>>a=43>>>b=[3]>>>a=[3]>>>id(a)43513741124351374184>>>id(b)>>>a is b>>>a[0]=4False>>>a==bTrue>>>b[3]>>>b=a # b就是a的引用,占得是同一块地址,而且当a的内容改变时,b也会随之改变,这和上面>>>a=[3]# int对象不同,我也不知道为啥要这么搞。[4]>>>a[0]=4>>>b

很多同学看到这肯定是一锅浆糊了,其实就是一个原则,能用==就不用is。除了一种情况,那就是判断对象是否是None。

>>>ifa is None:...pass

浅拷贝和深拷贝

>>>a=[3]>>>b=a[:]#通过切片赋值,返回的是a的浅拷贝>>>id(a)4351273944>>>id(b)1406026383497204351374184>>>id(a[0])>>>a is b>>>id(b[0])#list的地址不同140602638349720False>>>a[0]is b[0]#浅拷贝,只拷贝了a的壳[],里边的内容仍然是同一个东西,同样的idTrue>>>a==bTrue>>>a[0]==b[0]True>>>a[0]=4# 但是,b的内容不会随着a的变化而变化>>>b[3]

浅拷贝拷贝了最外层容器,副本中的元素是原容器中元素的引用

我们再看一个例子

>>>Anndy=['Anndy',['age',24]]>>>Tom=Anndy[:]>>>id(Anndy)>>>Cindy=list(Anndy)43513740404351374616>>>id(Tom)4351373968>>>id(Cindy)(['Anndy',['age',24]],['Anndy',['age',24]],['Anndy',['age',24]])>>>print(Anndy,Tom,Cindy)# 看起来是创建了三个不同的对象,因为他们的id各不相同>>>print(Anndy,Tom,Cindy)>>>Tom[0]='Tom'>>>Cindy[0]='Cindy'# 如果想修改某一个人的名字也没有什么问题(['Anndy',['age',24]],['Tom',['age',24]],['Cindy',['age',24]])# 现在我们想把Tom的年龄修改为12岁>>>Tom[1][1]=12# 震惊!所有人的年龄都变成了12!!!>>>print(Anndy,Tom,Cindy)(['Anndy',['age',12]],['Tom',['age',12]],['Cindy',['age',12]])>>>print([id(x)forx in Anndy])[4351366224,4351374112]# 第一个姓名元素的地址不同,但是第二个列表是同一个[4351366368,4351374112]# 看第二个列表的地址>>>print([id(x)forx in Tom])[4351323592,4351374112]# 看第二个!>>>print([id(x)forx in Cindy])

构造方法或切片 [:] 做的是浅拷贝。如果所有元素都是不可变的(比如名字字符串,修改的时候会重新创建对象,仅仅包括原子对象的元组也属于这种情况),那么这样没有问题,还能节省内存。但是,如果有可变的元素,可能就会导致意想不到的问题,正如刚刚,修改一个人的年龄,所有人的年龄都发生了变化。

所以,如果你想要深拷贝,应该这么写

>>>import copy>>>Anndy=['Anndy',['age',24]]>>>Tom=copy.deepcopy(Anndy)>>>print(Tom,Anndy)>>>Tom[1][1]=12(['Anndy',['age',12]],['Anndy',['age',24]])#这样写就没问题了

另外

不知道刚才你有没有注意到

>>>a=3>>>b=3140602638349720>>>id(a)>>>id(b)140602638349720# 相同!>>>a is bTrue

Python会对比较小的整数对象进行缓存缓存起来。当整数比较大的时候就会重新开辟一块内存。

>>>a=999>>>b=999140602638469952>>>id(a)>>>id(b)>>>a is b140602638469904# 不同!False

这仅仅是在命令行中执行,而在保存为文件执行,结果是不一样的,这是因为解释器做了一部分优化。

#!/usr/bin/env pythona=3b=3a=99999print(a is b)b=99999Trueprint(a is b)结果:True[Finished in0.0s]

这也是为什么我屡屡用is而不用==,程序运行良好的原因。

总结

1. python中,尽量不要用is, 除非判断对象是否为None。

2. a is b(相同)一定意味着a == b(相等),而a == b(相等) 不一定 a is b (相同)这点比较好理解

3. 如果函数中传参等,需要引用、拷贝的,注意是否生成了一个新的对象,即使生成了,内部元素是否是同一个对象的引用?尤其注意切片的使用。必要的时候用copy模块进行深拷贝而不要用切片这种浅拷贝形式。

>>>>阅读全文

你可能感兴趣的:(古灵精怪的python——地址,浅拷贝与身份运算符)