首先抛出一个问题,吸引读者的阅读兴趣(如果您觉得这个不是问题,那么这篇文章不适合您:)
请看如下代码:
>>> a = 3
>>> b = 3
>>> a == b
True
>>> a is b
True
>>> b = a
# 这没问题
>>> a = 3
>>> a == b
# 这看起来也很合理
True
>>> a is b
True
>>> a is b
>>> a = (2,3)
>>> b = (2,3)
>>> b = a
False # ???why?
>>> a == b
True
>>> a is b
True
True # ???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)
140602638349720
140602638349720
>>> c = a # a的引用复制给c,在内存中其实是指向了用一个对象
>>> id(c)
>>> a is b
True
True
>>> a is c
True
>>> b is c
我们看到,上面a,b,c的地址相同,所以他们互相之间”相同“
而相等则两者之间的数值对应相等
>>> a = 3
>>> b = a
>>> b
>>> a = 4
3
>>> b = [3]
>>> a = [3]
>>> id(a)
4351374112
4351374184
>>> id(b)
>>> a is b
>>> a[0] = 4
False
>>> a == b
True
>>> b
[3]
>>> b = a # b就是a的引用,占得是同一块地址,而且当a的内容改变时,b也会随之改变,这和上面
>>> a = [3]
# int对象不同,我也不知道为啥要这么搞。
[4]
>>> a[0] = 4
>>> b
很多同学看到这肯定是一锅浆糊了,其实就是一个原则,能用==就不用is。除了一种情况,那就是判断对象是否是None。
>>> if a is None:
... pass
浅拷贝和深拷贝
>>> a = [3]
>>> b = a[:] #通过切片赋值,返回的是a的浅拷贝
>>> id(a)
4351273944
>>> id(b)
140602638349720
4351374184
>>> id(a[0])
>>> a is b
>>> id(b[0]) #list的地址不同
140602638349720
False
>>> a[0] is b[0] #浅拷贝,只拷贝了a的壳[],里边的内容仍然是同一个东西,同样的id
True
>>> a == b
True
>>> a[0] == b[0]
True
>>> a[0] = 4 # 但是,b的内容不会随着a的变化而变化
>>> b
[3]
浅拷贝拷贝了最外层容器,副本中的元素是原容器中元素的引用
我们再看一个例子
>>> Anndy = ['Anndy', ['age', 24]]
>>> Tom = Anndy[:]
>>> id(Anndy)
>>> Cindy = list(Anndy)
4351374040
4351374616
>>> 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) for x in Anndy])
[4351366224, 4351374112] # 第一个姓名元素的地址不同,但是第二个列表是同一个
[4351366368, 4351374112] # 看第二个列表的地址
>>> print ([id(x) for x in Tom])
[4351323592, 4351374112] # 看第二个!
>>> print ([id(x) for x 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 = 3
140602638349720
>>> id(a)
>>> id(b)
140602638349720 # 相同!
>>> a is b
True
Python会对比较小的整数对象进行缓存缓存起来。当整数比较大的时候就会重新开辟一块内存。
>>> a = 999
>>> b = 999
140602638469952
>>> id(a)
>>> id(b)
>>> a is b
140602638469904 # 不同!
False
这仅仅是在命令行中执行,而在保存为文件执行,结果是不一样的,这是因为解释器做了一部分优化。
#!/usr/bin/env python
a = 3
b = 3
a = 99999
print(a is b)
b = 99999
True
print(a is b)
结果:
True
[Finished in 0.0s]
这也是为什么我屡屡用is而不用==,程序运行良好的原因。
总结
1. python中,尽量不要用is
, 除非判断对象是否为None
。
2. a is b
(相同)一定意味着a == b
(相等),而a == b
(相等) 不一定 a is b
(相同)这点比较好理解
3. 如果函数中传参等,需要引用、拷贝的,注意是否生成了一个新的对象,即使生成了,内部元素是否是同一个对象的引用?尤其注意切片的使用。必要的时候用copy模块进行深拷贝而不要用切片这种浅拷贝形式。
原文发布时间为:2018-08-12
本文作者:DeepWeaver
本文来自云栖社区合作伙伴“Python爱好者社区”,了解相关信息可以关注“Python爱好者社区”。