# is和==有什么区别?
面试官问:"Python中is和==有什么区别?"
很多录友会答"is比较地址,==比较值",再补一句"小整数用is也相等"。前半句方向没错,后半句就把CPython的实现优化当成了语言规则。这题要讲清楚两件事:is和==走的是两条完全不同的判断路径,以及为什么业务代码不能拿is做值比较。
前置知识是《Python变量保存的是什么?》:变量名只是绑定到对象的引用,多个名字可以绑定同一个对象。
# 简要回答
is比较对象身份,判断两个名字是否绑定到同一个对象,等价于id(a) == id(b)。它由解释器直接判断,不能被类重载。
==比较值是否相等,会调用对象的__eq__()方法,具体规则由类型自己定义。两个内容相同的列表,==为True,is为False。
使用上,判断None这类单例用is,比较值一律用==。小整数、短字符串用is有时也返回True,这是CPython缓存和驻留带来的实现细节,结果会随取值、运行方式和版本变化,不能依赖。
# 详细回答
# 两条不同的判断路径
a is b只问一个问题:这两个名字是不是指向同一个对象?id()返回对象在生命周期内唯一的身份标识,在CPython中就是对象的内存地址。
a == b则会调用a.__eq__(b),让对象自己决定"相等"是什么意思。列表的__eq__()逐个比较元素,自定义类可以按任意字段比较。
scores = [90, 85]
copied = [90, 85]
alias = scores
print(scores == copied) # True,内容相同
print(scores is copied) # False,两个独立的列表对象
print(scores is alias) # True,alias和scores绑定同一个列表
2
3
4
5
6
7
copied是另一个内容相同的新列表,所以==成立而is不成立。alias = scores没有复制任何东西,只是让新名字绑定到同一个对象,此时alias.append(100)会让scores也看到变化。这正是is真正有价值的场景:确认两个名字是否共享同一个可变对象,详见《可变对象与不可变对象》。

图中上半部分是名字与对象的绑定关系,下半部分是两种比较各自读取的信息:is只看两个名字是否指向同一个对象,==进入对象内部按__eq__()规则比较内容。
# 为什么判断None要用is
None在整个解释器中只有一个对象,是单例。判断"是不是None"本质上就是判断"是不是那一个对象",所以用is最贴切。
更实际的原因是:==可以被重载,is不能。
class AlwaysEqual:
def __eq__(self, other):
return True
obj = AlwaysEqual()
print(obj == None) # True,被__eq__欺骗了
print(obj is None) # False,身份判断不受影响
2
3
4
5
6
7
obj明明不是None,obj == None却返回True。用is None可以绕开任何自定义的相等性逻辑,PEP 8也明确建议和None比较时使用is或is not。True、False同样是单例,但条件判断里一般直接写if flag:,不写flag is True。
# 小整数缓存和字符串驻留不是语言规则
在CPython中,-5到256的小整数会被预先创建并复用,部分字符串也会被驻留(interning,同样内容的字符串共享一个对象)。于是会看到这样的结果:
small_a = int("100")
small_b = int("100")
print(small_a is small_b) # True,在CPython中命中小整数缓存
big_a = int("257")
big_b = int("257")
print(big_a is big_b) # False,超出缓存范围,是两个对象
print(big_a == big_b) # True
2
3
4
5
6
7
8
这里用int("...")在运行时构造整数,是为了避开编译器的常量合并。如果直接写a = 257、b = 257放在同一个脚本里,CPython编译时可能把两个相同常量合并成一个对象,a is b又会变成True;同样两行代码在交互式环境逐行输入,结果往往是False。
同一段逻辑,结果取决于取值范围、运行方式和解释器版本,这正说明它不能用来判断值是否相等。 面试时把它定性为CPython的内存优化即可,并明确说业务代码比较数字和字符串必须用==。从Python 3.8开始,对字面量使用is(如x is 1)还会触发SyntaxWarning,解释器本身也在提醒这种写法有问题。
# 知识拓展
面试官可能追问:
Q1:a is b为True,a == b一定为True吗?
不一定,最典型的反例是浮点数NaN。nan = float("nan")之后,nan is nan为True,但nan == nan为False,因为IEEE 754规定NaN不等于任何值,包括自己。
Q2:那为什么nan in [nan]返回True?
列表、字典等容器在判断成员时,会先比较身份,再比较相等,相当于x is item or x == item。同一个NaN对象身份相同,所以能被找到。这也说明容器内部的查找规则和单纯的==并不完全一样。
Q3:自定义类不实现__eq__(),==会怎么比较?
继承自object的默认__eq__()按身份比较,效果和is一样,只有同一个对象才相等。想按字段比较就要自己实现__eq__();实现后通常还要处理__hash__(),否则实例会变成不可哈希,不能放进集合或当字典键。
Q4:is not和!=有什么区别?
和正向一一对应:is not判断不是同一个对象,!=调用__ne__()判断值不相等。注意a is not b是一个整体运算符,不是a is (not b)。
Q5:is比==快,能不能为了性能用is?
is只比较身份,通常确实更轻量,但它回答的是另一个问题。为了省一点比较开销换来随时可能出错的结果,不值得。只有语义上就是比较身份(None、哨兵对象、确认是否共享对象)时才用is。
评论
验证登录状态...