当前位置:首页>python>Python的 is 与 == 的天壤之别:面试必坑,对象的“身份证”与“长相”

Python的 is 与 == 的天壤之别:面试必坑,对象的“身份证”与“长相”

  • 2026-10-11 07:31:59
Python的 is 与 == 的天壤之别:面试必坑,对象的“身份证”与“长相”

值相等 ≠ 是同一个对象,搞错可能出大 bug

一、还是一个让新手怀疑人生的例子

打开 Python 交互环境,输入:

a = 257
b = 257
print(a == b)   # True
print(a is b)   # True 还是 False?

输出:

True
False

a == b 是 True,但 a is b 是 False!
再试一个例子:

x = 5
y = 5
print(x is y)   # True

同样是比较两个整数,为什么 257 is 257 是 False,而 5 is 5 是 True?

这就是 Python 中 is 和 == 最经典的“坑”,也是面试高频题。

补充说明

这个现象源于 CPython 解释器的一个性能优化机制:小整数对象缓存(Small Integer Cache)。

  • 对于 -5 到 256 之间的整数:Python 在启动时会预先创建这些整数对象并存入缓存。当你在代码中使用该范围内的整数时,所有同值的变量名都会直接引用缓存中同一个对象。所以 5 is 5 为 True,因为它们指向内存中的同一个盒子。
  • 对于 257 或更大的整数:Python 不会自动缓存。每次执行 a = 257 都会在内存中新建一个整数对象。虽然两个对象的值相等(== 为 True),但它们的内存地址不同(id(a) != id(b)),因此 is 比较结果为 False。

注意:在 .py 文件或 Jupyter 单元格中,同一行内的相同大整数字面量可能会被编译器优化为同一个对象(常量折叠),但跨行仍可能不同。

在 Python 交互式命令行(REPL)中,每行代码单独编译,257 的两个字面量会创建不同对象。但在一个 .py 文件或 Jupyter 单元格中,编译器可能会优化同一个常量复用,导致 a is b 为 True。

故请记住不要依赖 is 来比较整数,应使用 ==。

二、核心区别:值 vs 身份

  • == 比较的是 值(value) 是否相等。
    就像问两个人:“你们长得一样吗?”

  • is 比较的是 身份(identity) —— 即两个变量是否指向内存中的 同一个对象。
    就像问:“你们是同一个人吗?”

在 Python 中,每个对象都有唯一标识,可用 id() 查看:

a = 257
b = 257
print(id(a))   # 例如 140234567890
print(id(b))   # 可能不同,比如 140234567920
print(a is b)  # False,因为 id 不同

总结一下:is 比较 id(a) == id(b),== 比较 a.__eq__(b)。

三、字符串驻留:另一个“意外”行为

字符串也有类似的“缓存”机制,称为字符串驻留(intern)。但不是所有字符串都会被驻留。

s1 = "hello"
s2 = "hello"
print(s1 is s2)   # True —— 短字符串、不含空格的常量会被驻留

s3 = "hello world"
s4 = "hello world"
print(s3 is s4)   # 可能 True 也可能 False,取决于实现和环境

s5 = "hello " + "world"# 编译时连接
s6 = "hello world"
print(s5 is s6)   # 通常 True(编译时优化)

s7 = "".join(["he", "llo"])
s8 = "hello"
print(s7 is s8)   # False —— 运行时创建的字符串不会被驻留

规则(CPython 实现):

  • 仅包含字母、数字、下划线的常量字符串会被自动驻留。
  • 含空格或特殊字符的常量字符串不一定驻留。
  • 运行时动态生成的字符串(如 join、切片)不会自动驻留。

实用建议:比较字符串值永远用 ==,不要用 is。

四、None、True、False 的特殊身份

None、True、False 是单例对象(每个只有唯一实例)。因此比较它们时常用 is:

x = None
if x isNone:      # ✅ 推荐
    print("x is None")

if x == None:      # ⚠️ 虽然可行,但不规范
    print("同样为真")

PEP 8 明确建议:与单例(如 None)比较时使用 is 或 is not。

五、列表的 [] is []

print([] is []) 
print([1, 2] is [1, 2]) 
print({} is {}) 
print(set() is set()) 

运行结果:

False
False
False
False

Why
因为每次使用字面量 [] 或 {} 或 set(),Python 都会在内存中创建一个 新的 列表/字典/集合对象。它们虽然内容相同,但身份不同,所以 is 返回 False。

对比:

a = [1, 2]
b = a
print(a is b)   # True —— 同一个对象

六、实战避坑:哪些情况必须用 is

1、检查是否为 None

deffunc(arg=None):
if arg isNone:    # ✅
        arg = []

2、检查是否为 True / False

通常不需要,直接用 if condition 即可。如果确实需要区分 True 和 False 对象(例如函数返回 True/False 或 1/0),建议用 is。

result = some_function()
if result isTrue:   # 明确要求是布尔 True,不是 1 或 "yes"
    ...

3、自定义类的 __eq__ 可能导致意外

classFoo:
def__eq__(self, other):
returnTrue# 永远相等

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

此时用 is 是唯一能判断两个变量是否指向同一实例的方式。

补充说明:

eq 是 Python 中用来定义 == 运算符行为的方法。 当你写下 a == b 时,Python 实际上会调用 a.eq(b),根据 eq 的返回值决定 a == b 是 True 还是 False。

is 运算符不调用任何特殊方法。是 Python 的内置运算符,它直接比较两个对象的 身份标识(identity),也就是内存地址。底层相当于比较 id(a) == id(b),但这是由解释器直接执行的操作,不会触发任何方法调用(也就是不会像 == 那样调用 __eq__)。

为什么没有方法?

  • is 的作用是判断两个变量是否指向同一个对象,这是非常底层的比较,与对象的“值”无关。
  • 如果允许重写 is 的行为(比如通过某个魔法方法),会破坏 Python 对象模型的基础,导致无法可靠判断对象身份(例如单例 None、True、False 的比较会变得不可预测)。

对比 == 的行为

运算符
是否调用方法
比较内容
==
调用 __eq__
值相等
is
不调用任何方法
身份相等(内存地址)

示例验证

classDemo:
def__eq__(self, other):
        print("__eq__ 被调用")
returnTrue

a = Demo()
b = Demo()

print(a == b)   # 输出:__eq__ 被调用 \n True
print(a is b)   # 输出:False(__eq__ 没有被调用)

结论:is 是直接操作对象指针的原子操作,不经过任何 Python 层面可重写的方法。

4、判断变量是否被重新赋值(用于缓存/单例模式)

_singleton = None

defget_instance():
global _singleton
if _singleton isNone:
        _singleton = MyClass()
return _singleton

七、总结速记

运算符
比较内容
常用场景
示例
==
值相等
比较数字、字符串、列表内容
a == 3、s == "hello"
is
身份(内存地址)
与 None 比较、判断是否同一对象
x is None、a is b

口诀:

值比用 ==,身份用 is;
None 单例用 is,整数小池别依赖;
两个空列表,长相一样不是同一个人。

最后一句忠告:

99% 的情况下,你应该使用 ==。只有当你明确需要知道“这两个变量是否指向内存中的同一个盒子”时,才用 is。

最新文章

随机文章