1. id() 看起来“不唯一”
很多人以为:
id(x)
返回的是“唯一”的对象编号。
实际上它只保证:
在对象生命周期内唯一。
对象销毁后,内存地址可能复用。
例如:
a = [1, 2]
print(id(a))
del a
b = [3, 4]
print(id(b))
有时候两个 id 会一样。
因为:
CPython 的 id() 本质是内存地址
Python 有对象池和内存复用机制
所以:
id() != 全局唯一ID
2. is 对小整数表现异常
经典问题:
a = 256
b = 256
print(a is b)
结果:
True
但:
a = 257
b = 257
print(a is b)
有时是:
False
原因:
CPython 会缓存小整数:
[-5, 256]
这些对象提前创建并复用。
因此:
256 is 256
本质是:
同一个对象
但大整数不一定缓存。
更离谱的是:
x = 1000
y = 10 * 100
print(x is y)
不同 Python 版本可能结果不同。
因为:
编译器常量折叠
字节码优化
REPL 与脚本模式差异
都会影响缓存行为。
3. 字符串驻留(intern)不稳定
例如:
a = "hello"
b = "hello"
print(a is b)
通常:
True
但:
a = "hello world!"
b = "hello world!"
print(a is b)
可能变成:
False
因为:
Python 会对部分字符串做 intern 缓存:
标识符风格字符串
短字符串
编译期常量
但不是全部。
例如:
import sys
a = sys.intern("hello world!")
b = sys.intern("hello world!")
print(a is b)
才会稳定为:
True
4. len() 有时 O(1),有时不是
很多人认为:
len(x)
永远是 O(1)。
其实不一定。
对于:
list
tuple
dict
set
长度直接缓存。
但自定义类:
class A:
def __len__(self):
print("called")
return 100
每次:
len(a)
都会重新调用。
因此:
len()
行为是否缓存,取决于对象实现。
5. hash() 缓存导致行为反直觉
字符串哈希会缓存:
hash("hello")
第一次会计算。
之后直接复用。
但:
class A:
def __hash__(self):
print("hash")
return 1
每次:
hash(obj)
都会重新执行。
而 tuple 又更特殊:
hash((1, 2, 3))
tuple 会缓存自己的 hash。
但如果里面包含不可 hash 对象:
([1],)
又会直接报错。
6. globals() 与局部变量不同步
例如:
x = 1
def test():
x = 2
print(locals())
x = 3
print(locals())
你会发现:
locals()
不是实时字典。
CPython 会对局部变量做优化:
使用数组槽位
并不总同步到 dict
因此:
locals()['x'] = 100
通常不会真正修改局部变量。
但:
globals()['x'] = 100
却能修改全局变量。
这是因为:
globals() 真的是模块字典
locals() 很多时候只是“快照”
7. eval() 与缓存字节码
Python 会缓存编译结果:
eval("1+2")
内部会:
编译 AST
转字节码
执行
而某些场景下:
compile()
生成的 code object 会被重复使用。
因此:
eval()
性能表现有时差异很大。
8. 默认参数缓存
经典坑:
def f(x=[]):
x.append(1)
return x
调用:
print(f())
print(f())
输出:
[1]
[1, 1]
因为:
默认参数只创建一次。
这是函数对象级缓存。
9. functools.lru_cache 与可变对象
例如:
from functools import lru_cache
@lru_cache()
def f(x):
return x
下面会报错:
f([1,2])
因为:
缓存 key 必须可 hash。
但更隐蔽的是:
f((1,2))
如果 tuple 内部对象状态变了,缓存逻辑可能产生“伪一致性”问题。
10. bool() 与单例缓存
True is 1
是:
False
但:
True == 1
却是:
True
因为:
bool
本质继承自 int。
并且:
True
False
是全局单例。
这些“不一致”本质来自:
CPython 性能优化
对象池
intern 机制
字节码常量折叠
哈希缓存
局部变量优化
单例复用
因此 Python 有一句老话:
不要依赖实现细节去写逻辑。
尤其不要:
is 比较数字和字符串
正确写法应该始终是:
==
除非你明确在判断:
None
True
False
这种单例对象。