Python的is和==把我坑惨了,原来对象比较的水这么深

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

先跟你说个真事儿。

上周五晚上,我写了一段用户登录的代码。逻辑很简单------从数据库查出用户信息,判断是不是管理员,决定跳转到哪个后台页面。

代码长这样:

scss 复制代码
user = get_user_from_db(user_id)  # 返回一个字典
if user['role'] is 'admin':
    redirect_to_admin_page()
else:
    redirect_to_user_page()

你猜怎么着?所有管理员登录后都跳转到了普通用户页面。

我盯着屏幕看了半小时,数据库里role字段明明白白写着'admin',打印出来也是'admin',可if条件就是进不去。

后来把is换成==,一切正常了。

就这一个字符的区别,我熬到凌晨三点。

从那以后我才真正搞明白,Python里is和==根本不是一回事。今天我就把这次踩坑的教训好好跟你说说。

先记住一句话

在深入之前,先记住这句核心区别:

==比较的是值(内容),is比较的是身份(内存地址)。

用大白话讲:

  • ==问的是:这两个东西长得一不一样?
  • is问的是:这两个东西是不是同一个东西?

长得一样的东西,不一定是同一个东西。就像双胞胎长得一模一样,但他们是两个人。==看长相,is看身份证号。

Python里每个对象都有三个基本属性:id(身份标识) 、type(数据类型) 和value(值) 。is比的是id,==比的是value。

场景一:列表------最直观的对比

先看一段代码:

ini 复制代码
a = [1, 2, 3]
b = a
c = [1, 2, 3]

print(a == b)   # True
print(a is b)   # True
print(a == c)   # True
print(a is c)   # False

同样都是[1, 2, 3],为什么a is b是True,a is c却是False?

因为b = a这句,b只是给a取了个别名 ,a和b指向的是内存里同一个列表对象。

而c = [1, 2, 3]是新创建 了一个列表。虽然内容和a一模一样,但它们是内存里两个不同的对象。

你可以用id()函数验证一下:

bash 复制代码
print(id(a))  # 比如 4364243328
print(id(b))  # 4364243328,和a一样
print(id(c))  # 4364202696,不一样

这就是为什么a is c是False------它们的"身份证号"不同。

场景二:整数------那个把我骗得最惨的坑

整数比较是新手最容易翻车的地方。

看这个:

ini 复制代码
a = 256
b = 256
print(a is b)   # True

c = 257
d = 257
print(c is d)   # False

同样是两个相等的整数,256返回True,257返回False。

这什么逻辑?

因为Python有个"小整数对象池" 。

Python启动的时候,会把-5到256之间的整数提前创建好,放在一个池子里。以后你用这个范围内的任何数字,Python都直接从池子里拿,不会新建。

所以a = 256和b = 256,两个变量拿到的其实是同一个整数对象。

但257不在池子里,每次赋值都会新创建一个对象 。所以c is d是False------值一样,但不是同一个东西。

这不就坑了吗? 我今天用is比较100没问题,明天换成1000就出bug了。这种不确定性,谁受得了?

场景三:字符串------时灵时不灵

字符串也有类似的坑。

ini 复制代码
s1 = "hello"
s2 = "hello"
print(s1 is s2)   # 通常是 True

但换个写法:

ini 复制代码
s1 = "hello world!"
s2 = "hello world!"
print(s1 is s2)   # 可能是 False

Python对字符串也有"驻留"机制------某些简单的字符串会被缓存复用。但规则很复杂,跟字符串长度、内容都有关系。

你永远不确定is会不会返回True 。所以比较字符串值的时候,永远用==。

场景四:自定义类------又一个隐形陷阱

自己定义的类,==的行为也不一样。

ini 复制代码
class Person:
    def __init__(self, name):
        self.name = name

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

print(p1 == p2)   # False
print(p1 is p2)   # False

两个Person对象,名字一样,但==和is都返回False。

因为默认情况下,自定义类的==比较的也是内存地址 ,和is没有区别。

如果想让==比较内容,需要自己实现__eq__方法:

python 复制代码
class Person:
    def __init__(self, name):
        self.name = name
    
    def __eq__(self, other):
        return self.name == other.name

p1 = Person("张三")
p2 = Person("张三")
print(p1 == p2)   # True
print(p1 is p2)   # False

到底什么时候用is?

说了这么多坑,那is是不是就没用了?

当然不是。is有它专属的使用场景:

1. 判断None

这是Python官方推荐的做法:

python 复制代码
# ✅ 正确
if data is None:
    print("数据为空")

# ❌ 不推荐
if data == None:
    print("数据为空")

因为None在Python里是单例 ------整个程序里只有一个None对象。用is判断既准确又高效。

2. 判断单例

类似的,True、False也是单例,也可以用is。

3. 检查两个变量是否指向同一个对象

比如你想确认两个变量是不是引用了同一个列表/字典实例。

除了这些情况,比较值请一律用==。

为什么is比==快?

顺便说个冷知识:is比==快。

因为is只是比较两个内存地址是否相同,就是一个整数比较。而==要调用对象的__eq__()方法,可能涉及复杂的值比较逻辑。

而且is不能被重载 ,行为是固定的。==可以被重载,不同类的==行为可能不一样。

但别为了这点性能就用is比较值------正确性永远比性能重要。

总结一下

运算符 比较什么 通俗理解 使用场景
== 值是否相等 长得像不像 绝大多数情况
is 是否是同一个对象 是不是同一个人 判断None、单例、对象身份

记住这几个原则:

  1. 比较值,永远用==------不管是数字、字符串、列表还是字典
  2. 判断None,用is------这是Python社区的共识
  3. 别拿is比较整数和字符串------小整数池和字符串驻留会让结果时灵时不灵
  4. 自定义类要实现__eq__ ------不然==和is没区别

那次熬夜之后,我给自己定了个规矩:除了判断None,一律用==。

如果你现在代码里还在用is比较字符串或者大整数,赶紧去改了吧。别等到凌晨三点才发现问题。

相关推荐
Apifox44 分钟前
Apifox 9 月更新|CLI 能力升级、GitLab 私有化部署接入与产品体验优化
前端·后端·测试
鱼弦1 小时前
Agent智能体 vs 传统运维:职业天花板的3倍差距?
后端
暗夜行者之光1 小时前
LangGraph 实战:用 LangSmith 可视化追踪 AI 智能体执行轨迹
后端
鱼弦1 小时前
Agent 的工具选择策略:从硬编码到动态决策
后端
Lost of 程序猿1 小时前
建造者模式实战:告别“十参数构造函数“的数据导出任务
后端·设计模式·c#·asp.net
SensorFlow1 小时前
ClickHouse 埋点事件表:先验证时间与重复,再谈漏斗看板
后端
Dreams_l2 小时前
基于SpringBoot实现的抽奖系统(一)
java·spring boot·后端
余槐i2 小时前
rollbackFor 不设置,Checked 异常不回滚:Spring 默认规则与两行修复的取舍
java·spring boot·后端·spring·事务管理
孙启超2 小时前
【AI开发之Rust】第 17 课:项目总览与核心架构 —— AI 助手 Rust 核心从 0 到 1
开发语言·后端·rust
EatFan3 小时前
JunoYi 框架实践:Spring Boot 项目为什么拆成 framework、module、server 三层?
java·spring boot·后端·framework·module·模块化·junoyi