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

简介: 一篇用真实踩坑经历讲透Python中`is`与`==`区别的技术文:前者比内存地址(身份),后者比值内容。通过登录Bug、列表、小整数池、字符串驻留、自定义类等典型场景,厘清使用边界——**仅判断`None`/单例时用`is`,其余一律用`==`**。(239字)

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

先跟你说个真事儿。

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

代码长这样:

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换成==,一切正常了。

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

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

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

先记住一句话

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

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

用大白话讲:

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

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

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

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

先看一段代码:

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()函数验证一下:

print(id(a))  # 比如 4364243328
print(id(b))  # 4364243328,和a一样
print(id(c))  # 4364202696,不一样

这就是为什么a is cFalse——它们的“身份证号”不同。

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

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

看这个:

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了。这种不确定性,谁受得了?

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

字符串也有类似的坑。

s1 = "hello"
s2 = "hello"
print(s1 is s2)   # 通常是 True

但换个写法:

s1 = "hello world!"
s2 = "hello world!"
print(s1 is s2)   # 可能是 False

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

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

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

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

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__方法:

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官方推荐的做法:

# ✅ 正确
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比较字符串或者大整数,赶紧去改了吧。别等到凌晨三点才发现问题。

目录
相关文章
人工智能 缓存 前端开发
6361 22
人工智能 JavaScript 开发工具
3368 6
缓存 JavaScript Shell
1585 2
开发工具 Swift git
1228 1
Shell API 调度
900 2
|
14天前
|
存储 弹性计算 缓存
阿里云服务器租赁费用:新版租赁收费标准及活动报价参考
本文更新了2026年阿里云全系列云服务器租赁活动报价,所有特惠资源均可前往阿里云活动中心选购,整体覆盖从个人入门到企业级高性能场景的全梯度需求。其中轻量应用服务器主打极致性价比,2核2G峰值200M带宽配置每日10点、15点限时抢购价仅38元/年,2核4G配置379元/年起;高性价比的经济型e实例、通用算力型u2i实例覆盖2核4G至4核32G全档位,适配开发测试与中小型企业业务;搭载英特尔至强6处理器的第九代c9i企业级实例算力较上代提升20%,支撑高并发生产环境,不同实例规格价差清晰,用户可根据自身业务负载与预算灵活选型。
2143 121
阿里云服务器租赁费用:新版租赁收费标准及活动报价参考
|
15天前
|
人工智能 程序员 API
Codex 接入 DeepSeek-V4-Flash:还能补上识图,提供两套方案
Codex 接入 DeepSeek-V4-Flash 怎么配?本文覆盖 CLI 与桌面端,再用 qwen3-vl-flash 补识图,两套方案可直接照做
1817 13
安全 机器人 API
660 2
缓存 人工智能 算法
743 1

热门文章

最新文章