下面是一篇专门讲 is 等"相等/身份"判断的说明文,会对"到底比的是地址还是值"这个问题展开说明,并顺带讲几个常见容易混淆的运算符和关键字。
一、先说结论:is 比较的是"对象身份",不是值
在 CPython 这种主流实现里,可以粗略理解为:
is比较的是两个变量是不是同一个对象(同一块内存里的同一个东西)。
常见写法:
python
a is b # 身份比较:是否为同一个对象
a is not b # 身份不相等
而:
python
a == b # 值比较:值是否相等(由类型的 __eq__ 决定)
a != b # 值不相等
1. "地址"这个说法该怎么理解?
在 C、C++ 等语言里,你可能会说"比较地址"。
在 Python 层面并不会直接暴露内存地址,但可以用 id(obj) 来理解为"对象身份":
python
id(obj) # 返回一个在该对象生命周期中唯一的整数标识
在 CPython 中,这个 id 通常就是该对象在内存中的地址(实现相关,不是语言保证,但足够帮助理解)。
于是可以理解为:
python
a is b ≈ id(a) == id(b)
a is not b ≈ id(a) != id(b)
二、is 与 == 的区别:几个典型例子
1. 对于可变对象(list、dict 等)
python
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b) # True,值相等
print(a is b) # False,不是同一个列表对象
如果你让两者指向同一个对象:
python
c = a
print(a == c) # True
print(a is c) # True,同一个对象
2. 对于不可变对象(int、str 等)
python
x = 1000
y = 1000
print(x == y) # True,值相同
print(x is y) # 可能是 False(不同实现/不同场景可能不同)
在 REPL / 小脚本里,x is y 有时会意外变成 True,这涉及常量缓存/驻留(interning),后面会说,但你应该记住:
判断数值、字符串等是否"值相等",始终用
==,不要指望is。
三、为什么有时候 a is b 会"意外"为 True?
1. 小整数缓存(CPython 的实现细节)
CPython 中对部分小整数做了缓存(通常是 [-5, 256]),以提升性能:
python
a = 100
b = 100
print(a is b) # 在 CPython 中通常是 True(共享同一个小整数对象)
但这是解释器实现细节,不是语言规范保证。你不能依赖这一行为写逻辑。
2. 字符串驻留(interning)
一些字符串也会被复用,尤其是:
- 标识符形式的字符串(如变量名、函数名)
- 常量字符串字面量
python
s1 = "hello"
s2 = "hello"
print(s1 is s2) # 在许多情况下是 True,但不能依赖
但一旦涉及拼接、运行时生成,情况就变化了:
python
s1 = "he" + "llo" # 常量折叠,可能仍然是驻留
s2 = "".join(["he", "llo"])
print(s1 == s2) # True
print(s1 is s2) # 不一定 True
原则:
- 判断字符串内容是否相同,用
== - 不要写依赖
is的字符串比较逻辑
四、什么时候应该用 is?
1. 与单例对象比较:None、True、False
Python 里有一些"单例"对象,全局只有一个实例,例如:
NoneTrueFalse- 某些库中的特定单例(如
sentinel对象)
判断是否为 None 的推荐写法:
python
if x is None:
...
而不是:
python
if x == None: # 不推荐
...
原因:
x == None会调用x.__eq__(None),由类型自己决定比较逻辑,可能被重载,甚至返回奇怪的结果x is None是纯身份比较,更精确表示"是否就是那个唯一的 None 对象"
类似地,当你自己设计一个"特殊标记"对象时,也可以用 is 来判断:
python
SENTINEL = object() # 独特标记
if value is SENTINEL:
...
2. 检查对象是否为同一实例
比如你在缓存/池化(pool)逻辑里,确认有没有拿到同一对象:
python
if obj is cached_obj:
...
五、其他"类似"的关键字/运算符:到底判断什么?
这里列出几个经常和 is 搞混的,同时说明它们判断的到底是什么。
1. == / !=:值相等比较
- 调用对象的
__eq__/__ne__方法,判断"值"是否相等。 - 对于内建类型,已经有合理实现;对于自定义类,可以自行重写。
示例:
python
class User:
def __init__(self, id, name):
self.id = id
self.name = name
u1 = User(1, "Alice")
u2 = User(1, "Alice")
print(u1 == u2) # 默认情况 False ------ 因为 User 没有实现 __eq__
print(u1 is u2) # False ------ 完全不同的对象
如果你希望"只要 id 一样就算同一个用户",可以:
python
class User:
def __init__(self, id, name):
self.id = id
self.name = name
def __eq__(self, other):
if isinstance(other, User):
return self.id == other.id
return NotImplemented
u1 = User(1, "Alice")
u2 = User(1, "Alice")
print(u1 == u2) # True(值意义下相等)
print(u1 is u2) # False(仍不是同一对象)
总结:
==是"值相等",is是"同一对象"。
2. in / not in:成员测试
in 并不比较"地址"或"值相等",它是"在不在里面"。
对不同容器有不同语义:
- 对列表/元组等序列:逐个元素用
==比较 - 对集合:基于散列,近似 O(1) 查找,用
==判断同一个元素 - 对字典:检查"键"是否存在
python
lst = [1, 2, 3]
print(1 in lst) # True(用 == 逐个比较)
print(4 in lst) # False
s = {1, 2, 3}
print(1 in s) # True
d = {"a": 1, "b": 2}
print("a" in d) # True,检查键
print(1 in d) # False
成员测试的本质:"是否存在等值元素" ,这里的相等是由 == 决定,不是 is。
3. is vs == 在 in 中的潜在混淆
有时你会看到类似代码:
python
None in seq
这背后是:
- 逐个元素,用
==与None比较
如果你想"列表里是否包含那个确切的对象实例 obj",而不是"包含一个值相同的对象",你可能需要:
python
any(item is obj for item in seq)
4. is 与 bool 上下文
记住一点:is 返回的是布尔值 True / False,常用于条件判断:
python
if x is None:
...
而对象本身在布尔上下文中也会被转换成 True / False(通过 __bool__ 或 __len__):
python
if some_list: # 空列表为 False,非空列表为 True
...
这与 is 完全是两码事,不要混淆"真假"和"是否为同一个对象"。
六、岔开一点:几个常被误会"比地址"的场景
1. 集合与字典中的"同一个元素"
python
s = set()
a = [1, 2]
b = [1, 2]
s.add(a)
print(b in s) # False ------ 虽然值相同,但列表不可哈希,代码本身会报错
对于可哈希对象(如 int、str、不可变 tuple):
python
s = set()
a = (1, 2)
b = (1, 2)
s.add(a)
print(b in s) # True ------ 基于 hash + ==,不是 is
set/dict 判断"是否存在",依据的是:
hash(obj):散列值(快速定位)==:真正的相等判断(排除散列冲突)
跟"地址"无关。
七、总结与实践建议
1. 记住这三句话
is判断是否为同一个对象(同一身份),不管值是否相同。==判断值是否相等 ,由类型的__eq__决定。- 判断数值、字符串、容器内容是否相同,用
==,不要用is。
2. 使用 is 的正确场景
- 判断是否为
None:if x is None:/if x is not None:
- 判断是否为某个明确的单例对象 :
- 自己设计的
SENTINEL = object()
- 自己设计的
- 判定两个变量是否指向同一个实例(如缓存、对象池)
3. 使用 == 的场景
- 几乎所有"内容比较"的场景:
- 数字是否相等
- 字符串内容是否相同
- 列表、字典等数据结构内容是否相同
- 自定义类希望根据字段判断相等性时(记得重写
__eq__)