用函数调用避开编译期常量折叠,验证运行期算出的整数落在-5~256区间时is才为True,以及字符串驻留跟整数缓存是两套不同机制。
直接写 c=1000; d=1000 测is,容易被编译器的"常量折叠"干扰,不是真的在测运行时缓存。
代码:用函数调用确保是运行期才计算出的值def make(x, y): return x + y # 参数是变量,无法在编译期被折叠成常量 runtime_1000 = make(200, 800) literal_1000 = 1000 print(runtime_1000 is literal_1000)
如果直接写 e = 200 + 800,CPython的编译器会在编译阶段就把它算成1000(peephole优化),这样测出来的 is True 其实是"同一个字面量在同一个模块里被去重",跟运行时的整数缓存池是两回事。用函数参数相加,保证了这个1000是程序运行起来之后才被计算出来的。
用真正运行期计算的值,测试区间内外的表现。
结果运行期算出的1000 is 字面量1000: False(超出缓存区间,各自独立对象) 运行期算出的100(在缓存区间内) is 字面量100: True(缓存生效) 5次运行期各自独立算出的100,id是否全部相同: True 5次运行期各自独立算出的1000(超出缓存区间),id是否全部相同: False
验证了CPython确实维护着一个 -5~256 的小整数单例池:只要计算结果落在这个区间,不管是字面量还是运行期算出来的,都会复用同一个对象,is 判断为True。一旦超出这个区间,每次创建都是独立的新对象,is 判断依赖的是对象地址,自然就是False——这也是为什么很多人拿 is 判断整数相等,小数字时"看起来一直是对的",换成大一点的数字就突然出问题。
字符串没有固定的数值区间,驻留(intern)靠的是"看起来像不像标识符"。
结果s1='hello', s2='hello'(字面量): s1 is s2 是 True s3='hello world', s4='hello world'(含空格): s3 is s4 是 True(同一模块内的相同字面量,一样会被去重) s5用join动态拼出'hello': s5 is s1 是 False,s5 == s1 是 True 手动sys.intern两次'hello world': s6 is s7 是 True
同一个模块里相同的字符串字面量,会被自动去重(这跟整数字面量的编译期去重是同一套机制,不是-5~256那种运行时缓存池)。但只要字符串是运行期动态构造出来的(比如 join),哪怕内容一模一样,也是一个新对象,is 为False。如果确实需要"内容相同就复用同一个对象",可以手动调用 sys.intern(),强制把这个字符串纳入驻留池。
is 判断的是对象身份,不是值——小整数和常见字面量字符串"看起来"is和==结果一样,只是CPython在这两类场景下做了缓存/去重优化,让它们凑巧复用了同一个对象;这从来不是语言保证,想判断值相等,永远应该用 ==,不是 is。
每一步都是本文上面用Python 3.14跑出来的原始结果。
sys.intern:手动触发字符串驻留