先给结论:is 比的是两个变量是不是同一个对象(内存地址相同,相当于 id(a) == id(b)),== 比的是两个对象的值相不相等(调用 a.__eq__(b))。一个是"身份证号"层面的比较,一个是"长得像不像"的比较。这俩从根上就不是一回事,混用是 Python 新手最常踩、也最隐蔽的坑。
下面用 5 个真实场景把这层窗户纸捅破,每个都附代码和可照搬的结论。
一、小整数池:为什么 256 is 256 是 True,但 257 is 257 是 False
Python 为了省内存,把 -5 到 256 之间的整数在解释器启动时预先创建好并缓存,全局共用同一批对象。所以:
a = 256
b = 256
print(a is b) # True,命中小整数池,是同一个对象
c = 257
d = 257
print(c is d) # False,超出缓存范围,各建各的
注意:这个结论只在交互式环境逐行执行时稳定。如果在同一个 .py 文件的函数外连续写 c = 257; d = 257,部分 Python 实现会因为常量折叠把 257 合并成一个对象,is 反而可能返回 True。这正是坑点——结果依赖运行环境,你没法保证。
正确写法:比较数值永远用 ==,别用 is。
二、字符串驻留:为什么有的字符串 is 相等,有的不相等
字符串也有类似的"驻留"(intern)机制,但规则比整数更迷惑:
s1 = "hello"
s2 = "hello"
print(s1 is s2) # True,编译期常量,被合并
s3 = "hello world"
s4 = "hello world"
print(s3 is s4) # 通常 True(短字符串常量)
a = "hello"
b = "hel" + "lo" # 编译期拼接,等价于 "hello"
print(a is b) # True
x = "hello"
y = "hel"
z = y + "lo" # 运行时拼接,不是常量
print(x is z) # False!运行时生成的新对象
关键差异:只有编译期就能确定的字符串常量才可能被驻留;凡是涉及运行时变量拼接的,几乎一定产生新对象。所以 is 比较字符串,永远不可靠。
正确写法:字符串相等用 ==。
三、单例判等:只有 None / True / False 才该用 is
有一类对象 Python 保证全局只有一个实例,这时候用 is 是规范且推荐的:
x = None
print(x is None) # ✅ 正确写法
print(x == None) # ⚠️ 能跑,但不符合 PEP 8,且可能被 __eq__ 重载坑
flag = True
print(flag is True) # ✅ 判断布尔身份用 is
为什么 == None 危险?因为某些对象可以重载 __eq__,让 obj == None 返回 True,但 obj is None 一定是 False。判断"是不是没有值",用 is None 才是铁律。PEP 8 明确建议:None 的比较用 is / is not。
四、容器内容相同,is 永远是 False
哪怕两个列表长得一模一样,只要不是同一个对象,is 就返回 False:
list_a = [1, 2, 3]
list_b = [1, 2, 3]
print(list_a == list_b) # True,值相等
print(list_a is list_b) # False,两个独立对象
dict_a = {"k": 1}
dict_b = {"k": 1}
print(dict_a is dict_b) # False
这里 == 会递归比较每个元素(列表元素再比 ==,字典比键值对)。而 is 只看是不是内存里同一个盒子。绝大多数业务场景你要的都是"内容一样",所以用 ==。
五、自定义类:重写 __eq__ 后,== 和 is 会彻底分家
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
return isinstance(other, Point) and self.x == other.x and self.y == other.y
p1 = Point(1, 2)
p2 = Point(1, 2)
print(p1 == p2) # True,__eq__ 说值相等
print(p1 is p2) # False,是两个不同实例
重写 __eq__ 只影响 ==,完全不影响 is。更进一步,如果 __eq__ 里写了会抛异常的逻辑(比如比较类型不符时 raise),== 会炸,而 is 永远不会炸——因为 is 根本不调用任何方法,纯粹比内存地址。
这也是为什么:当你想判断"这两个引用指向的不是同一个东西"时,用 a is not b 最稳。
六、一张表看清区别
| 维度 | is | == |
|---|---|---|
| 比较内容 | 对象身份(id 相同) | 对象值(__eq__ 结果) |
| 是否调用方法 | 不调用,纯内存地址比较 | 调用 __eq__,可能抛异常 |
| 适用场景 | None / True / False 单例判等 | 数值、字符串、容器等内容比较 |
| 小整数/驻留影响 | 受缓存机制干扰,结果不稳定 | 不受影响,稳定 |
| 性能 | 极快(一次指针比较) | 略慢(可能递归遍历) |
| 常见误用 | 用 is 比 256 以外的整数/字符串 | 几乎无(除非 __eq__ 有坑) |
七、什么时候该用哪个(记住这三条就够了)
- 判断是不是
None、是不是某个单例:用is/is not,这是 Python 社区的硬性规范。 - 判断数值、字符串、列表内容是否相等:一律用
==,别碰is。 - 需要确认"两个变量是不是严格指向同一个对象"(比如防止重复传同一个可变对象):用
a is b。
最后再强调一遍:is 快,但它比较的是身份不是值;一旦你关心的是"值",就老老实实写 ==。靠小整数池和字符串驻留的巧合写出 a is b 返回 True,是埋了一颗随时换环境就炸的雷。
你之前有没有被 257 is 257 在脚本里返回 True、到交互环境却返回 False 这种事坑过?评论区说说你踩过的 is / == 陷阱。