为什么你的 257 is 257 是 True:常量合并会骗过缓存测量

举报
yd_232353574 发表于 2026/11/11 08:11:27 2026/11/11
【摘要】 当天在 Python 3.13.14 实测:同函数两个 257、256+1、列表取值、函数返回字面量,以及 257 is 257,写 is 全得 True;把四种写法放进同一函数后 dis 显示整数常量只有 [257, 0],这是编译期常量合并而非整数缓存。3.13 对 is 比字面量发 SyntaxWarning。只有 int("257") 这类运行时来源才测出真实边界,逐点扫描确认本机复用区间为负 5 到 256。结论:数值比较一律用等号。

同一个 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 就先问一句:这里要比的是身份,还是值。绝大多数时候答案是值。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。