值相等 ≠ 是同一个对象,搞错可能出大 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 的比较会变得不可预测)。
对比 == 的行为
示例验证
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 | | | x is None、a is b |
口诀:
值比用 ==,身份用 is;
None 单例用 is,整数小池别依赖;
两个空列表,长相一样不是同一个人。
最后一句忠告:
99% 的情况下,你应该使用 ==。只有当你明确需要知道“这两个变量是否指向内存中的同一个盒子”时,才用 is。