为什么你的 257 is 257 是 True:常量合并会骗过缓存测量
同一个 257,为什么测出来两个答案
关于 Python 小整数缓存,社区里已经讲得很清楚:一段区间内的整数会被复用,is 比它们碰巧能给出 True。可自己动手验证时,最容易卡在第一步——照着例子写 257 is 257,结果有时是 True,而文章里说 257 不该被复用。这不是文章写错,也不是版本变了,是测法本身把常量合并当成缓存了。
先看看合并有多容易触发
在本机 Python 3.13.14 上,把几种常见写法都跑一遍:
def f():
a = 257
b = 257
return a is b
print(f()) # 同一个函数里两个字面量
print(257 is 257) # 同一个表达式
print((256 + 1) is (256 + 1)) # 两个独立的算术式
print([257][0] is [257][0]) # 两个独立列表里取出来的值
def g(): return 257
print(g() is g()) # 函数返回字面量
输出:
True
True
True
True
True
全部 True。容易误以为「算术算出来的、从列表里取出来的、函数返回的,总该是新对象了吧」,实际并没有。
当天另外写了一个把四种写法放进同一个函数的版本来看常量池:
def f():
a = 257
b = 257
c = 256 + 1
d = [257][0]
return a is b, a is c, a is d
f() 返回 (True, True, True),而它的整数常量只有 [257, 0](0 是列表下标)。也就是说 257 在编译期只存了一份,几个看起来来源不同的值其实都指向它。编译器把同一段代码里值相同的常量合并成一份,这和运行时缓存是两回事,但它足以让「257 是否被复用」这个测量彻底失效。
顺带一提,3.13 对 is 直接比整数字面量会给出 SyntaxWarning: "is" with 'int' literal. Did you mean "=="?。解释器已经在提醒这个写法本身有问题了。
要绕开,得让值在运行时才算出来
只有来源不是编译期常量,才能测到真实的复用行为。比如从字符串转换:
print(int("256") is int("256"))
print(int("257") is int("257"))
print(int("-5") is int("-5"))
print(int("-6") is int("-6"))
print(int("-7") is int("-7"))
输出:
True
False
True
False
False
当天用 int(str(n)) 逐点扫过边界,负 8、负 6 是 False,负 5、负 4、0、1、255、256 是 True,257、258、1000 是 False。合起来就是本机 CPython 3.13 上复用的区间为负 5 到 256,两头都算进去。注意 int(s) is int(s)、eval("257") is eval("257") 也都能绕开合并,而 250 + 7 is 250 + 7 仍然是 True——关键不在写法看起来多"动态",而在值是不是编译期就能确定。
这个区间是解释器的实现优化,目的是让常用的小整数不必反复新建对象。不同版本、不同解释器未必一样,这里只说本机实测的结果,不把它当成语言保证。
字符串那段别照搬整数
字符串也有对象复用,但判定比整数复杂,跟长度、字符构成、是不是编译期常量都有关,运行时拼出来的串未必享受同样的待遇。本节没有对字符串逐点实测,不在这里给具体边界;能确定的只是它不像整数这样能用一条明确的区间线概括,用 is 比值同样不可靠。
回到该怎么写代码
is 比的是两个名字是否指向同一个对象,等号比的是值是否相等。缓存让小区间里这两件事碰巧重合,于是用 is 比值得出正确结果纯属巧合:
a = int("1000")
b = int("1000")
print(a == b) # True
print(a is b) # False
拿 is 比数值,等于把正确性建立在内部优化上。区间变了、换了解释器,或者只是换了一种写法让常量合并生效,判断结果就会翻转;用变量时不会有任何报错,只有直接比字面量才会收到上面那条 SyntaxWarning。数值比较一律用等号;is 只在明确要判断对象身份时才用,比如和 None 比较,或确认某个操作返回的是否就是原来那个实例。
所以真正值得记住的不是「负 5 到 256」这个数字,而是验证这类实现细节时,先确认自己的测法有没有把编译期常量合并混进来。看到 is 就先问一句:这里要比的是身份,还是值。绝大多数时候答案是值。
- 点赞
- 收藏
- 关注作者
评论(0)