Python 的 is 把我坑惨了,原来 == 和 is 在小整数池外完全是两码事

简介: 本文以一次深夜调试经历切入,揭示Python中`==`与`is`的本质区别:`==`比较值是否相等,`is`判断是否为同一对象。通过numpy.int64类型陷阱、小整数池(-5~256)、字符串驻留等实例,说明滥用`is`的隐患,并强调——除判空(`x is None`)等极少数场景外,一律应使用`==`。

一个让我熬夜到凌晨三点的 Bug

先说说我是怎么栽在这个坑里的。

去年有个项目,我要写一个缓存系统。逻辑很简单:从数据库里查出用户 ID,然后判断这个 ID 是不是 0——0 代表"游客",需要特殊处理。

代码大概是这样的:

def get_user_role(user_id):
   # user_id 是从数据库查出来的
   if user_id is 0:
       return "游客"
   return "注册用户"

本地测试的时候一切正常,user_id 为 0 时能正确返回"游客"。我就信心满满地部署上线了。

结果上线之后,监控系统疯狂报警——"游客"身份的判断时不时失效,有时候明明是 0,却被当成注册用户处理了。

我盯着代码看了两个小时,确认 user_id 的值确实是从数据库查出来的 0。Python 里 0 is 0 不是应该返回 True 吗?本地明明跑得好好的啊。

后来我打印了 user_id 的类型才发现——数据库驱动返回的 user_idnumpy.int64(0),不是 Python 内置的 int 类型。

numpy.int64(0) is 0 返回 False,而 numpy.int64(0) == 0 返回 True

那一刻我意识到:我一直在用 is 做值比较,但这个操作符根本不是干这个用的。

代理 IP 使用小技巧 让你的数据抓取效率翻倍 (64).png

改了一行代码——把 is 换成 ==——问题解决了。但那天晚上的三个小时再也回不来了。

== 和 is 到底有什么不一样?

先给出最简洁的答案:

  • == 比较的是值——两个东西"长得是不是一样"
  • is 比较的是身份——两个东西"是不是同一个东西"

用大白话说:**== 问你俩是不是双胞胎,is 问你俩是不是同一个人**。

看几个最简单的例子:

a = [1, 2, 3]
b = [1, 2, 3]
c = a

print(a == b)  # True,两个列表的值一模一样
print(a is b)  # False,两个列表是不同的对象,只是内容碰巧相同
print(a is c)  # True,a 和 c 指向的是同一个对象

这个好理解对不对?一个变量赋值,一个新建列表,虽然内容一样但确实是两个独立的东西。

问题在于——对于数字和字符串这种基础类型,Python 搞了一些"小动作",让你产生了"is 也能比较值"的错觉。

小整数池:为什么 256 以内 is 没问题,257 就不行了?

先看这段代码:

a = 100
b = 100
print(a is b)  # True

再看这段:

a = 1000
b = 1000
print(a is b)  # False

同样的写法,只是数字从 100 换成了 1000,结果完全不一样。

你是不是觉得 Python 疯了?

其实 Python 没疯,它只是在背后做了一件优化的事。

Python 在启动的时候,会预先创建好一批整数对象,范围是 -5 到 256。这些整数是"常驻内存"的,不管你写多少遍,用的都是同一个对象。

这就是所谓的 **"小整数池"**。

所以当你写 a = 100b = 100 时,Python 不会新建两个整数对象,而是直接把两个变量指向小整数池里的那个 100。所以 a is bTrue——它们确实是同一个对象。

但当你写 a = 1000 时,1000 不在小整数池里,Python 会新建一个整数对象。再写 b = 1000,又新建一个。虽然值一样,但两个对象在内存里是不同的,所以 a is bFalse

这就是为什么你平时用 is 比较小整数一直没问题,一到生产环境数据量大了、数字超过 256 就莫名失灵的根本原因。

同样的道理也适用于字符串。Python 有一种叫 "字符串驻留" 的机制——代码里写死的字符串字面量,比如 "hello",会被复用同一个对象。但动态生成的字符串,比如 "".join(["h", "e", "l", "l", "o"]),就是新建的对象。

a = "hello"
b = "hello"
print(a is b)  # True,字面量复用

a = "".join(["h", "e", "l", "l", "o"])
b = "".join(["h", "e", "l", "l", "o"])
print(a is b)  # False,动态生成的两个不同对象

你看,你用 is 比字符串有时候是对的,有时候是错的,完全取决于字符串是怎么来的。这谁能受得了?

None、True、False:这几个是例外

有一类对象是可以用 is 的,而且应该用 is——就是那些全局唯一的单例

  • None
  • True
  • False

这几个对象在 Python 整个运行过程中有且只有一个实例。不管你怎么引用,它们都是同一个对象。

所以判断一个变量是不是 None,标准写法是:

if x is None:   # 正确
if x == None:   # 不推荐,虽然能工作但语义不对

判断布尔值也是同理:

if flag is True:    # 可以,但通常直接 if flag: 更简洁
if flag == True:    # 不推荐

为什么官方推荐用 is 来比较 None?因为 None 是单例,用 is 比较的是身份,速度更快,而且语义更准确——你确实想知道"这个变量是不是那个唯一的 None",而不是"这个变量的值是否等于 None"。

一个更隐蔽的坑:自定义类的实例

这个问题在自定义类里尤其容易踩。

class Person:
   def __init__(self, name):
       self.name = name

p1 = Person("张三")
p2 = Person("张三")

print(p1 == p2)  # False,默认比较的是内存地址
print(p1 is p2)  # False,本来就是两个对象

如果想让 p1 == p2 返回 True,你需要给类实现 __eq__ 方法:

class Person:
   def __init__(self, name):
       self.name = name

   def __eq__(self, other):
       if not isinstance(other, Person):
           return False
       return self.name == other.name

这时候 p1 == p2 就是 True 了,但 p1 is p2 仍然是 False——因为它们是两个不同的对象,只是名字一样而已。

这就是 ==is 的本质区别:== 的行为是可以自定义的,而 is 是内存地址的直接比较,永远无法改变。

什么时候用 ==,什么时候用 is?

经过那次三个小时的教训,我给自己定了一个铁律:

比较值的时候,永远用 ==,除非你有 100% 的把握要用 is

什么时候可以有那 100% 的把握?只有这三种情况:

  • 判断 Nonex is None
  • 判断 True / Falsex is True(但通常直接用 if x:if not x:
  • 判断是否是同一个对象(比如在缓存里判断两个引用是否指向同一个实例)

除此之外,**一律用 ==**。

不管是你觉得"这个数字肯定很小不会超 256",还是"这个字符串肯定是字面量",都不要赌。生产环境的数据你永远猜不到它会变成什么样。

# 永远这样做
if user_id == 0:
   return "游客"

# 不要这样做
if user_id is 0:   # 万一 user_id 不是 int 呢?万一超过 256 呢?
   return "游客"

写在最后

那天凌晨三点,我把 is 改成 == 之后,缓存系统恢复了正常。关掉 IDE 的时候我在想——如果当初写代码的时候能多想一想"我到底要比较什么",那三个小时完全不用浪费。

is== 的区别,写出来就是一句话,但真正理解它需要踩一次坑。

== 问的是"你俩值一样吗",is 问的是"你俩是同一个东西吗"。大多数时候你想问的都是前者,而不是后者。

小整数池和字符串驻留这些优化机制,本来是 Python 为了提升性能做的好事,但它们也让 is 的行为变得"时灵时不灵",反而成了陷阱的源头。

记住一句话:除非比较 None,否则默认用 == 这个习惯能帮你省下无数个深夜调试的时间。

希望你不用像我一样,等到凌晨三点才明白这个道理。

目录
相关文章
|
8天前
|
存储 弹性计算 缓存
阿里云服务器租赁费用:新版租赁收费标准及活动报价参考
本文更新了2026年阿里云全系列云服务器租赁活动报价,所有特惠资源均可前往阿里云活动中心选购,整体覆盖从个人入门到企业级高性能场景的全梯度需求。其中轻量应用服务器主打极致性价比,2核2G峰值200M带宽配置每日10点、15点限时抢购价仅38元/年,2核4G配置379元/年起;高性价比的经济型e实例、通用算力型u2i实例覆盖2核4G至4核32G全档位,适配开发测试与中小型企业业务;搭载英特尔至强6处理器的第九代c9i企业级实例算力较上代提升20%,支撑高并发生产环境,不同实例规格价差清晰,用户可根据自身业务负载与预算灵活选型。
1826 118
阿里云服务器租赁费用:新版租赁收费标准及活动报价参考
|
9天前
|
人工智能 程序员 API
Codex 接入 DeepSeek-V4-Flash:还能补上识图,提供两套方案
Codex 接入 DeepSeek-V4-Flash 怎么配?本文覆盖 CLI 与桌面端,再用 qwen3-vl-flash 补识图,两套方案可直接照做
1380 11
|
15天前
|
云安全 人工智能 运维
阿里云联动百位企业安全专家,共识Agent防御最佳实践
当Agent成为新员工,你的安全边界在哪里?
1962 9
阿里云联动百位企业安全专家,共识Agent防御最佳实践
|
9天前
|
编解码 人工智能 安全
2核4G/4核8G/8核16G阿里云服务器如何选择实例?经济型e、通用算力型u2i与计算型c9i选哪个?
本文介绍了阿里云2核4G、4核8G、8核16G三档主流配置下经济型e、通用算力型u2i和计算型c9i三种实例的最新活动价格与适用场景。同配置下三者价差显著,以2核4G为例,经济型e低至599.93元/年,计算型c9i则高达1742.08元/年。文章详细解析了各实例的性能定位:经济型e适合轻负载入门场景,u2i兼顾稳定算力与性价比,c9i凭借第9代至强处理器与芯片级安全能力支撑高性能业务。同时提示用户可叠加满减优惠券享受折上折,建议根据业务负载与预算综合决策。
550 113
|
6天前
|
编解码 弹性计算 云计算
MiniMax-H3 视频生成模型 — 一键部署与使用指南
MiniMax-H3是MiniMax开源的33B全模态视频生成模型,支持文生视频、图生视频、参考生视频三种模式,原生输出2K/15秒带立体声音频视频,已原生适配ComfyUI,并可通过阿里云计算巢一键部署。(239字)
|
21天前
|
人工智能 前端开发 Linux
Codex 桌面版安装 + CC Switch 接入第三方 API 完整教程(2026 最新)
2026最新教程:手把手教你安装Codex桌面版,通过CC Switch v3.17.0一键接入Fenno等国产API(兼容OpenAI Responses格式),跳过账号登录,完整启用代码审查、多步任务与上下文感知功能。零基础友好,全程图文实操。(239字)
3219 5
|
9天前
|
人工智能 JSON Shell
2026AI漫剧本地全开源方案(附各个软件模型链接),8G显卡也能流畅运行
这是一套完全本地化部署的AI漫剧生成技术链路:涵盖LLM剧本分镜生成、FLUX文生图(IP-Adapter人脸锁定)、StoryDiffusion时序连贯控制、LTX-2.3唇形同步视频生成,及ComfyUI全流程调度。零云端费用,仅耗硬件算力,单集2–4小时可产出竖屏短视频,适配抖音/B站分发。
|
7天前
|
人工智能 API 开发工具
2026 零基础本地 AI 漫剧完整实操教程(8G 笔记本显卡可用|附可直接复制命令与代码)
本方案提供完全离线、本地运行的漫剧全自动制作流程:RTX3060/4050 8G显卡即可驱动,涵盖Qwen写分镜→ComfyUI统一角色绘图→LTX2.3图生微动画→Qwen3-TTS本地配音→FFmpeg自动合成,全程无水印、免API、不限次。专为低显存优化,解决变脸、闪烁、爆内存三大痛点。(239字)

热门文章

最新文章