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 bTruea 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 cFalse------它们的"身份证号"不同。

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

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

看这个:

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

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

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

这不就坑了吗? 我今天用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. 判断单例

类似的,TrueFalse也是单例,也可以用is

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

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

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

为什么is==快?

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

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

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

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

总结一下

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

记住这几个原则:

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

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

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

相关推荐
乐橙开放平台1 小时前
物业 SaaS 笔记:子账号 Policy 按通道隔离,At_ 管控面 / St_ 数据面治理 accessToken
后端·物联网·音视频
fatcoder1 小时前
玩转Nginx 04 — 反向代理:给 nginx 接上后端
前端·后端·nginx
雨落倾城夏未凉1 小时前
halcon核心-颜色识别/颜色控件转换(十)
后端
SomeB1oody1 小时前
【RustyML入门】5.2. 分类指标
开发语言·后端·机器学习·rust·教程
明月_清风3 小时前
Pi Agent 深度解析:开源极简终端 AI 编码代理的终极指南
前端·后端·ai编程
程序员cxuan3 小时前
DeepSeek Harness 必装的插件公布了!
人工智能·后端·程序员
fatcoder3 小时前
玩转Nginx 03 — location 匹配规则:让不同的路径各回各家
前端·后端·nginx
wno7043 小时前
Spring Boot JdbcTemplate配置Druid多数据源
java·spring boot·后端
月才3 小时前
Spring Boot 接口参数校验从入门到精通
后端