Python 里 `is` 和 `==` 到底差在哪?5 个让你怀疑人生的案例 + 对照表

0 阅读5分钟

先给结论: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 / == 陷阱。