Python 的字符串 intern(驻留)机制,是最容易出现:
“明明内容一样,
为什么有时候 is=True,
有时候又 False?”
的经典陷阱。
很多人第一次看到:
a = "hello"
b = "hello"
print(a is b)
输出:
True
会误以为:
Python 的字符串都会自动复用内存。
实际上根本不是。
1. == 和 is 根本不是一回事
先区分:
a == b
比较:
值是否相等
而:
a is b
比较:
是不是同一个对象
2. 最诡异的地方
例如:
a = "python"
b = "python"
print(a is b)
结果:
True
但:
a = "hello world"
b = "hello world"
print(a is b)
有时候:
True
有时候:
False
不同:
Python 版本
解释器
REPL
文件执行
都可能不同。
3. 什么是字符串 intern
Python 为了优化:
会把部分字符串放进:
intern pool(驻留池)
后续重复字符串:
直接复用对象。
避免:
重复分配内存
重复 hash
提高 dict 查找效率
4. 哪些字符串容易被 intern
最常见:
标识符风格字符串
例如:
"abc"
"hello"
"my_var"
通常会自动 intern。
因为:
它们很像 Python 变量名。
5. 含空格的字符串就不稳定
例如:
a = "hello world"
b = "hello world"
print(a is b)
结果依赖:
编译优化
常量折叠
当前环境
因为:
带空格字符串不一定自动 intern。
6. REPL 和脚本结果可能不同
这是最经典的坑。
交互模式:
>>> a = "hello world"
>>> b = "hello world"
>>> a is b
True
但脚本运行:
False
因为:
REPL 会缓存当前输入行里的常量。
7. 动态生成字符串通常不会 intern
例如:
a = "hello"
b = "".join(["he", "llo"])
print(a == b)
print(a is b)
输出:
True
False
因为:
join() 生成了新对象。
8. 小整数缓存类似但不完全一样
很多人把:
256 is 256
和字符串 intern 混为一谈。
其实:
小整数缓存
字符串 intern
是两套机制。
虽然思想类似:
复用常用对象
9. sys.intern()
Python 提供了手动 intern。
例如:
import sys
a = sys.intern("hello world")
b = sys.intern("hello world")
print(a is b)
输出:
True
10. 为什么 dict 查找会受益
Python dict:
大量依赖字符串 key。
如果:
key1 is key2
那么:
甚至不需要逐字符比较。
直接:
指针比较
速度极快。
这也是:
为什么:
变量名
属性名
类名
通常会被 intern。
11. 编译期常量折叠的迷惑行为
例如:
a = "hello" "world"
b = "helloworld"
print(a is b)
输出:
True
因为:
编译器直接优化成一个常量。
12. 更诡异的 case
例如:
a = "a" * 20
b = "aaaaaaaaaaaaaaaaaaaa"
print(a is b)
有时:
True
有时:
False
因为:
不同版本优化策略不同。
13. 函数边界行为不同
例如:
def f():
return "hello"
def g():
return "hello"
print(f() is g())
很多时候:
True
因为:
代码对象共享常量池。
14. 长字符串更容易失败
例如:
a = "x" * 10000
b = "x" * 10000
print(a is b)
通常:
False
因为:
超长字符串默认不会 intern。
否则内存池会爆炸。
15. Unicode 字符串更复杂
例如:
a = "你好"
b = "你好"
print(a is b)
有时候:
True
有时候:
False
取决于:
编码
Python 版本
编译器优化
Unicode 的 intern 行为没 ASCII 稳定。
16. 不要依赖 is 比较字符串
这是核心原则。
错误:
if s is "admin":
正确:
if s == "admin":
因为:
intern 行为不是语言保证。
17. 为什么有时线上才出 bug
开发环境:
is True
生产环境:
is False
原因可能是:
Python 版本不同
PyPy vs CPython
debug 模式
编译优化变化
导致:
字符串是否 intern 改变。
18. PyPy 行为可能完全不同
PyPy:
JIT 更多。
intern 策略可能:
更激进
更保守
所以:
依赖:
is
会更危险。
19. 一个特别隐蔽的坑
例如:
token = request.GET["type"]
if token is "vip":
某些请求:
居然成功。
某些失败。
因为:
有时 web 框架内部复用了字符串。
有时没复用。
这种 bug 非常难查。
20. 官方其实不保证
Python 官方从未保证:
相同字符串一定共享对象
intern:
只是优化策略。
不是语言语义。
总结
字符串 intern 的本质:
Python 对部分字符串进行对象复用优化
导致:
a == b
和:
a is b
有时结果看起来“诡异”。
核心记住:
== 比较值
is 比较对象身份
intern 是实现细节
不要依赖字符串 is