一、什么是身份运算符?
身份运算符用于比较两个对象是否是同一个对象——也就是判断它们是否在内存中占据同一块地址。
a = [1, 2, 3]b = a # b 引用 a 的同一个列表c = [1, 2, 3] # c 是另一个新列表,但值相同print(a is b) # True(a和b是同一个对象)print(a is c) # False(a和c值相同,但不是同一个对象)print(a == c) # True(值相等)
二、2种身份运算符
| | | |
is | | a is b | True |
is not | | a is not b | True |
三、is 运算符详解
3.1 基本用法
x = [1, 2, 3]y = x # y 指向 x 的同一个列表z = [1, 2, 3] # z 是一个新列表print(x is y) # True(同一对象)print(x is z) # False(不同对象,即使值相同)print(x == z) # True(值相同)
3.2 查看对象id
x = [1, 2, 3]y = xz = [1, 2, 3]print(id(x)) # 输出内存地址,如 140234567890print(id(y)) # 和 x 相同print(id(z)) # 不同的地址
四、is 与 == 的区别(重点)
a = [1, 2, 3]b = [1, 2, 3]print(a == b) # True(值相同)print(a is b) # False(不是同一个对象)c = aprint(c is a) # True(同一个对象)
图解概念:
a ──→ [1,2,3] (内存地址0x100)b ──→ [1,2,3] (内存地址0x200) # 不同对象,值相同c ──→ 指向a的同一个对象 (0x100)
五、何时使用 is,何时使用 ==
5.1 用 is 的场景
# ✅ 正确:判断 None 用 isx = Noneif x isNone:print("x 是 None")# 而不是用 ==if x == None: # 也能工作,但不规范pass# 判断布尔值也用 isflag = Trueif flag isTrue:print("flag 是 True")
5.2 用 == 的场景
# ✅ 比较值相等用 ==a = 100b = 100if a == b:print("值相等")# 而不是用 isif a is b: # 可能成立也可能不成立(依赖缓存),不可靠pass
六、Python 的对象缓存机制
6.1 小整数缓存
Python 为了性能,会缓存**-5 到 256** 之间的小整数。这些范围内的整数在内存中只有一个对象。
a = 256b = 256print(a is b) # True(在缓存范围内)c = 257d = 257print(c is d) # False(超出缓存,创建了新对象)print(c == d) # True(值仍然相等)
6.2 字符串驻留
某些短字符串也会被缓存(驻留),但规则复杂,不要依赖。
s1 = "hello"s2 = "hello"print(s1 is s2) # 可能 True(因驻留),但不保证# 动态生成的字符串通常不驻留s3 = "".join(["h", "e", "l", "l", "o"])print(s1 is s3) # 通常 False
6.3 结论
永远不要用 is 比较数字或字符串的值,即使在某些时候返回 True,也是不可靠的。坚持 is 用于 None 和布尔值,== 用于值比较。
七、is not 运算符
is not 是 is 的反向操作,判断两个对象不是同一个。
x = [1, 2]y = [1, 2]z = xprint(x isnot y) # True(不同对象)print(x isnot z) # False(同一对象)# 等价于 not (x is y),但更可读
常用场景:
if x isnotNone:print("x 不是 None")
八、常见陷阱与注意事项
陷阱1:用 is 比较数字
a = 1000b = 1000print(a is b) # False(不可靠)print(a == b) # True(正确)# 不要写 if a is 1000: 这样的代码
陷阱2:用 is 比较字符串
a = "hello world"b = "hello world"print(a is b) # 可能 True 或 False,取决于解释器实现# 永远用 == 比较字符串
陷阱3:误以为 is 和 == 等价
a = Noneb = Noneprint(a is b) # True,正确print(a == b) # True,也正确,但语义不同# 但遇到其他情况就不同了a = []b = []print(a is b) # Falseprint(a == b) # True
陷阱4:可变对象的 is
a = []b = aa.append(1)print(b is a) # True,还是同一个对象,b 也看到修改# 如果用 ==,b == a 当然也是 True,但 is 告诉我们它们本来就是同一个
陷阱5:在类中自定义 __eq__ 后 is 仍比较内存地址
classPerson:def__init__(self, name):self.name = namedef__eq__(self, other):returnself.name == other.namep1 = Person("张三")p2 = Person("张三")print(p1 == p2) # True(根据 __eq__)print(p1 is p2) # False(不同对象)
九、实战案例
案例1:检查 None
defprocess_data(data):if data isNone:print("数据为空,返回默认值")return []# 处理 datareturn [x * 2for x in data]print(process_data(None)) # []print(process_data([1, 2, 3])) # [2, 4, 6]
案例2:单例模式判断
classSingleton: _instance = Nonedef__new__(cls):if cls._instance isNone: cls._instance = super().__new__(cls)return cls._instances1 = Singleton()s2 = Singleton()print(s1 is s2) # True,确保是同一个实例
案例3:缓存对象复用
classImageLoader: _cache = {} @classmethoddefload(cls, path):if path in cls._cache:print("从缓存加载")return cls._cache[path]# 模拟加载 image = f"Image data for {path}" cls._cache[path] = imagereturn imageimg1 = ImageLoader.load("a.jpg")img2 = ImageLoader.load("a.jpg")print(img1 is img2) # True,是同一个对象
案例4:判断是否为同一个变量
defmodify_list(lst): lst.append(4)print("在函数内修改了列表")original = [1, 2, 3]modify_list(original)print(original) # [1, 2, 3, 4]# 如果想检查传入的是否就是 original 本身,可以用 isdefmodify_safe(lst):if lst is original:print("你传入了原始列表,小心修改") lst.append(4)
案例5:使用 is not 进行安全检查
defsafe_divide(a, b):if b isNoneor b == 0: # 其实 b is None 和 b == 0 语义不同returnNonereturn a / b# 更清晰的写法defsafe_divide_v2(a, b):if b isNone:print("除数不能为 None")returnNoneif b == 0:print("除数不能为 0")returnNonereturn a / b
十、身份运算符速查表
| | |
| x is None | x == None |
| x is True | x == True |
| x is y | |
| x == y | x is y |
| x == y | x is y |
十一、总结与记忆口诀
身份运算符两个is 和 is not比较内存地址是否是同一个与 == 要分清== 比较值相等is 比较是否同None 判断用 is值比较用等号小整数有缓存但千万别依赖字符串有驻留也当不存在记住一条原则is 只用于 None以及 True False其他都用 ==