Python 的 is 和 == 到底怎么选:小整数缓存、字符串驻留与 None 判断的坑
有人写了个缓存命中判断,if user_id is 100000:,本地测试小 id 全对,上了生产大 id 全部命中失败,排查半天发现是is用错了。is和==看着都是「判断相等」,但一个比的是「是不是同一个对象」,一个比的是「值相不相等」。混用平时不出事,一旦踩中 Python 的对象缓存机制,就会出现「本地对、线上错」这种最难查的 bug。这篇把两者的区别、以及那几个反直觉的缓存坑讲清楚。
先划清楚:is 比身份,== 比值
==调用对象的__eq__方法,比较的是值是否相等。is比较的是两个变量是不是指向内存里同一个对象,等价于id(a) == id(b)。
a=[1,2,3]b=[1,2,3]print(a==b)# True,两个列表值相等print(aisb)# False,是内存里两个不同的列表对象c=aprint(cisa)# True,c 和 a 指向同一个列表这里a和b值一样但不是同一个对象,所以==为真、is为假。这是最该建立的直觉:大多数时候你想判断的是「值相等」,那就该用==。
坑一:小整数缓存,让 is 在小数字上「碰巧对」
Python 启动时会预先缓存 -5 到 256 之间的整数对象(小整数池),这个范围内的数字,无论你在哪写,拿到的都是同一个对象。于是:
a=100b=100print(aisb)# True —— 因为 100 在缓存范围内,是同一个对象x=100000y=100000print(xisy)# False —— 超出缓存范围,是两个不同对象print(x==y)# True —— 值当然相等这就是开头那个 bug 的真相:user_id is 100000,当 id 小的时候可能碰巧命中缓存返回 True,id 一大就永远是 False。用is判断整数相等是错的,哪怕它有时候「看起来对」。判断数值相等永远用==。
顺带一提,-5~256这个范围是 CPython 的实现细节,别依赖具体数字,换个 Python 版本或解释器都可能变——这更说明了不该拿is比数字。
坑二:字符串驻留(intern),同样是「有时对有时错」
Python 对字符串也有类似优化,叫intern(驻留):像标识符那样的短字符串(只含字母数字下划线的字面量)会被驻留成同一个对象:
a="hello"b="hello"print(aisb)# True,被驻留了# 但拼接出来的、或含特殊字符的,通常不驻留c="hello world"d="hello world"print(cisd)# 可能 False(取决于实现)# 运行时拼出来的更不保证e="hel"+"lo"# 编译期常量折叠,可能 Truef="".join(["h","e","l","l","o"])# 运行期拼接print(eisf)# 大概率 False结论一样:判断字符串内容相等,用==,永远别用is。驻留是解释器背着你做的优化,你不该依赖它,也不该被它坑。
is 真正该用在哪:和单例比较,尤其是 None
那is是不是就没用了?不是。is的正确用武之地是和「单例对象」比较——那些整个程序里只有一个实例的对象:None、True、False。其中最重要的是None:
deffind(items,target):result=lookup(items,target)# 可能返回 None,也可能返回 0、""、[]ifresultisNone:# 正确:明确判断「没找到」return"not found"returnresult判断 None 必须用is None/is not None,不能用== None。原因有两个:
None是全局唯一单例,is判身份最准确、最快,不用调__eq__。- 更重要的是安全性:如果用
== None,而对象自定义了__eq__,可能被改写出诡异行为:
classWeird:def__eq__(self,other):returnTrue# 恶意/疏忽:和任何东西都相等w=Weird()print(w==None)# True —— 被 __eq__ 骗了!print(wisNone)# False —— is 不会被 __eq__ 影响,如实反映身份is绕过__eq__直接比身份,所以判断「是不是那个唯一的 None」时它既快又不会被糊弄。PEP 8 明确规定:和 None 比较要用is/is not。
坑三:别用 is 判断 True/False,更别拿它当真值判断
x=1print(x==True)# True(1 == True,因为 True 本质是 1)print(xisTrue)# False(x 是整数 1,不是布尔单例 True)判断一个值是否为真,直接if x:就好,别写if x is True:——它只在 x 恰好是布尔True那个对象时才成立,x = 1都会挂。
一张速查表
| 你想判断 | 正确写法 | 别用 |
|---|---|---|
| 两个值是否相等 | a == b | a is b |
| 是不是 None | x is None | x == None |
| 是不是同一个对象 | a is b | a == b |
| 某个值是否为真 | if x: | if x is True: |
小结
==比值(走__eq__),is比身份(是不是同一个对象,等价于id()相等)。绝大多数「相等判断」需求都该用==。- 小整数缓存(-5~256)和字符串驻留会让
is在小数字/短字符串上「碰巧返回 True」,这是「本地对、线上错」类 bug 的经典来源——别拿is比数字和字符串。 is的正解是和单例比较,尤其is None/is not None:更快、且不会被自定义__eq__糊弄(PEP 8 强制要求)。- 判断真值用
if x:,别用is True。 - 一句话记忆点:比值用 ==,认对象(尤其 None)用 is;拿 is 去比数字或字符串,迟早被缓存坑。