刚学 Python 那会儿,我以为 == 就是判断相等,is 也是判断相等,区别只是写法不同。直到有一天写了这段代码:
a = [1, 2, 3]b = [1, 2, 3]print(a == b) # Trueprint(a is b) # False
当时我就懵了,明明内容一样,为什么 is 说不一样?
后来搞明白了,这俩压根不是一回事。
第一件事:== vs is
== 比较的是"值"
a = [1, 2, 3]b = [1, 2, 3]print(a == b) # True —— 内容相同
== 调用的是对象的 __eq__ 方法,比较的是两个对象的内容是否相等。
is 比较的是"身份"
a = [1, 2, 3]b = [1, 2, 3]print(a is b) # False —— 不是同一个对象
is 比较的是两个对象在内存中的地址(id()),也就是:是不是同一个东西。
a = [1, 2, 3]b = a # b 指向同一个对象print(a == b) # Trueprint(a is b) # True —— 确实是同一个对象print(id(a) == id(b)) # True
那 is 什么时候返回 True?
对于不可变对象(int、str、tuple),Python 会做缓存复用:
# 小整数缓存(-5 到 256)a = 256b = 256print(a is b) # True —— 同一个缓存对象a = 257b = 257print(a is b) # False —— 超出缓存范围,两个不同对象
# 字符串驻留a = "hello"b = "hello"print(a is b) # True —— 简单字符串会被驻留a = "hello world"b = "hello world"print(a is b) # 不一定 —— 含空格的字符串不保证驻留
什么时候用 is?
判断 None 的时候,必须用 is:
# 正确if x is None: print("x 是 None")# 不推荐if x == None: print("x 是 None")
为什么?
因为 == None 会调用 __eq__,某些库重写了这个方法可能导致意外行为。is None 比较的是身份,不可能出错。
其他场景用 == 就够了。
第二件事:深拷贝 vs 浅拷贝
先看一个例子
original = [[1, 2], [3, 4]]copy = original.copy() # 浅拷贝copy[0][0] = 999print(original) # [[999, 2], [3, 4]] —— 原来也被改了!
明明用了 .copy(),为什么原来的列表也变了?
浅拷贝:只拷贝外壳
original = [[1, 2], [3, 4]]copy = original.copy()
浅拷贝做的事情:
copy → [引用A, 引用B] ↓ ↓original → [引用A, 引用B] ↓ ↓ [1, 2] [3, 4] ← 内层列表只有一份
外壳(外层列表)是新的,但里面装的元素(内层列表)还是同一个引用。
所以 copy[0][0] = 999 修改的是内层列表,原始和拷贝共享同一个内层列表,自然都变了。
深拷贝:完全独立的副本
import copyoriginal = [[1, 2], [3, 4]]deep = copy.deepcopy(original)deep[0][0] = 999print(original) # [[1, 2], [3, 4]] —— 原来不受影响
深拷贝会递归地复制所有层级,内层列表也是全新的对象:
deep → [引用C, 引用D] ↓ ↓ [1, 2] [3, 4] ← 新的内层列表original → [引用A, 引用B] ↓ ↓ [1, 2] [3, 4] ← 原来的内层列表
常见的浅拷贝方式
lst = [1, 2, 3]copy1 = lst.copy() # 方法一copy2 = lst[:] # 方法二copy3 = list(lst) # 方法三# 字典d = {"a": [1, 2]}copy4 = d.copy() # 浅拷贝copy5 = dict(d) # 浅拷贝# 列表推导式也是浅拷贝copy6 = [x for x in lst]
什么时候用深拷贝?
当数据结构是嵌套的(列表套列表、字典套列表等),而且你需要完全独立的副本时:
import copy# 场景:游戏存档game_state = { "player": {"hp": 100, "inventory": ["sword", "shield"]}, "enemies": [{"type": "goblin", "hp": 50}]}# 浅拷贝:改存档会影响原始状态save = game_state.copy()save["player"]["hp"] = 0print(game_state["player"]["hp"]) # 0 —— 崩了# 深拷贝:完全独立save = copy.deepcopy(game_state)save["player"]["hp"] = 0print(game_state["player"]["hp"]) # 100 —— 没事
如果是简单的一维数据(纯数字列表、纯字符串列表),浅拷贝就够了。
对照表
| 场景 | 用什么 | 为什么 |
|---|
| 判断是否为 None | is None | 比较身份,不可能出错 |
| 比较两个值是否相等 | == | 比较内容 |
| 判断是否为同一个对象 | is | 比较内存地址 |
| 复制一维列表/字典 | .copy() / 切片 | 浅拷贝够用,性能好 |
| 复制嵌套结构 | copy.deepcopy() | 需要完全独立 |
| 函数传参防止被改 | copy.deepcopy() | 避免副作用 |
防坑清单
| 检查项 | 怎么查 |
|---|
用 is 还是 ==? | 判断 None 用 is,其他用 == |
| 复制会不会互相影响? | 嵌套结构必须用 deepcopy |
| 不确定对象是不是同一个? | print(id(a), id(b)) 看地址 |
一句话总结:
== 比较值,is 比较身份;浅拷贝拷外壳,深拷贝全拷贝。搞混了,数据就互相污染。
Python 的"相等"和"同一个"是两回事,"拷贝"和"深拷贝"也是两回事。概念不清,bug 不停。
评论区聊聊:你被 is 和 == 坑过吗?还是被浅拷贝坑过?
下期预告:为什么你的 for 循环这么慢?——数据结构选错了,性能差 100 倍
Python/ML/DL/大模型都折腾过,坑也踩了不少。