Python 中 is 与 == 的区别及底层实现

is判断对象身份(内存地址),==判断值相等(调用__eq__);仅对none、true、false等单例用is安全,数值和字符串比较必须用==。

is 判断的是对象身份,== 判断的是值相等

Python 里 is== 看似都能比“是不是一样”,但底层干的事完全不同:is 检查两个变量是否指向**同一个对象**(即内存地址是否相同),而 == 调用的是对象的 __eq__ 方法,比的是“逻辑上是否相等”。这意味着哪怕两个对象内容一模一样,只要不是同一个对象,is 就返回 False

常见错误现象:if x is True:if x is None: 写得顺手,但换成 if x is 1: 就可能翻车——因为整数小常量(-5 到 256)会被缓存复用,超出范围就不再稳定。

  • 只对 NoneTrueFalse 这类单例用 is 是安全且推荐的
  • is 比字符串、列表、自定义对象,基本等于赌运气
  • == 可被重载,比如 numpy.array([1,2]) == numpy.array([1,2]) 返回的是布尔数组,不是单个 True

小整数和短字符串的 is 行为是 CPython 实现细节,别依赖

CPython 为了节省内存,会把常用的小整数(-5 到 256)和某些短字符串(如标识符风格的)放进对象池,重复创建时直接复用。所以 100 is 100True,但 1000 is 1000 在交互式环境里有时是 True、有时是 False——取决于是否在同一个编译单元里被优化。

使用场景:你写脚本时偶然发现 "hello" is "hello" 成立,但这只是因为解释器做了字符串驻留(interning),不是语言规范保证的行为。换到 PyPy 或某些嵌入式 Python 环境,结果可能不同。

  • 不要用 is 来判断字符串内容是否相同,一律用 ==
  • 不要靠 is 测试数字是否等于某个值,除非是 None 或布尔单例
  • 想确认对象身份?用 id(a) == id(b) 更直白,也更容易被理解意图

自定义类中 == 的行为由 __eq__ 控制,is 完全绕过它

当你定义一个类,默认的 == 行为其实是继承自 object 的,也就是退化成 is——比较的是对象身份。所以如果你没写 __eq__a == ba is b 效果一样;但一旦你实现了 __eq__== 就按你的逻辑走,is 却始终只看地址。

Skybox AI

一键将涂鸦转为360°无缝环境贴图的AI神器

示例:

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 print(p1 is p2) # False

  • 实现 __eq__ 时,建议同时实现 __hash__(如果对象要进 set 或作 dict 键)
  • __eq__ 返回 NotImplemented,Python 会尝试调用 other.__eq__(self),这是双向比较机制
  • is 永远不会触发任何魔术方法,它就是纯粹的指针比较

None 检查必须用 is,不能用 ==

这是唯一被 PEP 8 明确规定的例外:if x is None: 是标准写法,if x == None: 不仅慢(触发 None.__eq__),还危险——因为用户可以重载 __eq__== None 返回 True,即使对象根本不是 None

示例:

class BadClass:
    def __eq__(self, other):
        return True  # 故意捣乱

x = BadClass() print(x == None) # True ← 错误地通过了 print(x is None) # False ← 正确拒绝

  • is None 是最快的 None 检查方式,也是唯一语义明确的方式
  • 同理,is Trueis False 也比 == True 更安全,但更常见的是直接写 if x:if not x:
  • 别写 if x is not None: 的反模式变体,比如 if not x is None:,可读性差且容易误读

最易被忽略的一点:很多人以为“== 比较值,is 比较地址”就够了,但真正麻烦的是那些看起来像值、实则带状态的对象——比如浮点数 nan,它连自己都不等于自己(float('nan') == float('nan')False),这时候 is 也救不了你,得用 math.isnan()。底层逻辑清楚,不代表所有边界都自动覆盖。

相关推荐:

如何在Python中使用asyncio.shield防止关键任务被取消?

asyncio.shield必须用于关键清理操作(如日志落盘、事务提交、连接关闭),防止被取消中断导致数据不一致;应只包裹协程本身(shield(coro)),而非Task,且内部子await需单独shield。 asyncio.shield什么时候必须用? 当一个asyncio.Task正在执行关...

为什么Python单例模式在多线程环境下会失效?

__new__中判空非原子操作会导致多线程重复创建实例;DCL通过无锁初判+加锁后复判解决,需配合__init__幂等防护。 __new__中的if判断不是原子操作 线程A执行到ifcls._instanceisNone时为True,刚准备调用super().__new__(cls),此时被调度挂起...

如何修复Python aiohttp请求时出现的ClientPayloadError?

ClientPayloadError表明服务端提前关闭连接,非客户端代码错误;常见于服务端超时、拒绝大响应或代理截断,需通过抓包、检查响应头、服务端日志及添加读取超时和异常处理来定位解决。 ClientPayloadError通常意味着服务端提前关闭了连接 这个错误不是客户端代码写错了,而是aioh...

为什么在大型Python项目中推荐使用Monorepo环境管理?

Monorepo是应对跨子包频繁修改、版本对齐成本高、CI耗时爆炸等痛点的止损方案;需统一依赖管理、确保本地与CI环境一致,并严守子包独立测试边界。 Monorepo不是“更先进”,而是对特定规模和协作模式的项目更可控——当Python项目中出现跨子包频繁修改、版本对齐成本高、CI耗时爆炸或本地开发...

为什么Python爬虫在解析复杂表格时推荐使用Pandas库?

pandas.read_html能自动解析HTML表格并修复合并单元格、多级表头等结构,但需合理配置match、header、skiprows等参数,并处理JS渲染、数据类型、空值等问题,否则易导致列错位、类型错误等隐患。 因为pandas.read_html能直接把HTML表格转成DataFram...

为什么在Python项目中推荐使用Poetry管理包依赖?

poetryinstall与pipinstall-rrequirements.txt的本质差异在于:前者是“解析后装”,基于poetry.lock中已验证的完整依赖图确保版本兼容并自动激活专属虚拟环境;后者是“盲装”,仅按文本顺序安装、不解析依赖冲突、不隔离环境,易导致静默覆盖和全局污染。 因为pi...