Python 的 is 和 == 到底怎么选:小整数缓存、字符串驻留与 None 判断的坑

Python 的 is 和 == 到底怎么选:小整数缓存、字符串驻留与 None 判断的坑 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(ab)# True,两个列表值相等print(aisb)# False,是内存里两个不同的列表对象caprint(cisa)# True,c 和 a 指向同一个列表这里a和b值一样但不是同一个对象,所以为真、is为假。这是最该建立的直觉:大多数时候你想判断的是「值相等」,那就该用。坑一:小整数缓存,让 is 在小数字上「碰巧对」Python 启动时会预先缓存 -5 到 256 之间的整数对象(小整数池),这个范围内的数字,无论你在哪写,拿到的都是同一个对象。于是:a100b100print(aisb)# True —— 因为 100 在缓存范围内,是同一个对象x100000y100000print(xisy)# False —— 超出缓存范围,是两个不同对象print(xy)# True —— 值当然相等这就是开头那个 bug 的真相:user_id is 100000,当 id 小的时候可能碰巧命中缓存返回 True,id 一大就永远是 False。用is判断整数相等是错的,哪怕它有时候「看起来对」。判断数值相等永远用。顺带一提,-5~256这个范围是 CPython 的实现细节,别依赖具体数字,换个 Python 版本或解释器都可能变——这更说明了不该拿is比数字。坑二:字符串驻留(intern),同样是「有时对有时错」Python 对字符串也有类似优化,叫intern(驻留):像标识符那样的短字符串(只含字母数字下划线的字面量)会被驻留成同一个对象:ahellobhelloprint(aisb)# True,被驻留了# 但拼接出来的、或含特殊字符的,通常不驻留chello worlddhello worldprint(cisd)# 可能 False(取决于实现)# 运行时拼出来的更不保证ehello# 编译期常量折叠,可能 Truef.join([h,e,l,l,o])# 运行期拼接print(eisf)# 大概率 False结论一样:判断字符串内容相等,用,永远别用is。驻留是解释器背着你做的优化,你不该依赖它,也不该被它坑。is 真正该用在哪:和单例比较,尤其是 None那is是不是就没用了?不是。is的正确用武之地是和「单例对象」比较——那些整个程序里只有一个实例的对象:None、True、False。其中最重要的是None:deffind(items,target):resultlookup(items,target)# 可能返回 None,也可能返回 0、、[]ifresultisNone:# 正确:明确判断「没找到」returnnot foundreturnresult判断 None 必须用is None/is not None,不能用 None。原因有两个:None是全局唯一单例,is判身份最准确、最快,不用调__eq__。更重要的是安全性:如果用 None,而对象自定义了__eq__,可能被改写出诡异行为:classWeird:def__eq__(self,other):returnTrue# 恶意/疏忽:和任何东西都相等wWeird()print(wNone)# True —— 被 __eq__ 骗了!print(wisNone)# False —— is 不会被 __eq__ 影响,如实反映身份is绕过__eq__直接比身份,所以判断「是不是那个唯一的 None」时它既快又不会被糊弄。PEP 8 明确规定:和 None 比较要用is/is not。坑三:别用 is 判断 True/False,更别拿它当真值判断x1print(xTrue)# True(1 True,因为 True 本质是 1)print(xisTrue)# False(x 是整数 1,不是布尔单例 True)判断一个值是否为真,直接if x:就好,别写if x is True:——它只在 x 恰好是布尔True那个对象时才成立,x 1都会挂。一张速查表你想判断正确写法别用两个值是否相等a ba is b是不是 Nonex is Nonex None是不是同一个对象a is ba 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 去比数字或字符串,迟早被缓存坑。