受这个关于缓存小整数和字符串的问题的启发,我发现了以下我不理解的行为。
>>> 1000 is 10**3
False
我以为我理解这种行为: 1000 是要缓存的。1000 和 10**3 指向 2 个不同的对象。但我错了:
>>> 1000 is 1000
True
因此,也许 Python 对计算的处理与“普通”整数不同。但这个假设也不正确:
>>> 1 is 1**2
True
如何解释这种行为?
受这个关于缓存小整数和字符串的问题的启发,我发现了以下我不理解的行为。
>>> 1000 is 10**3
False
我以为我理解这种行为: 1000 是要缓存的。1000 和 10**3 指向 2 个不同的对象。但我错了:
>>> 1000 is 1000
True
因此,也许 Python 对计算的处理与“普通”整数不同。但这个假设也不正确:
>>> 1 is 1**2
True
如何解释这种行为?
这里发生了两件不同的事情:Python 将int
文字(和其他文字)存储为带有已编译字节码的常量,并且将小整数对象缓存为单例。
当您运行时,1000 is 1000
只有一个这样的常量会被存储和重用。你真的在看同一个对象:
>>> import dis
>>> compile('1000 is 1000', '<stdin>', 'eval').co_consts
(1000,)
>>> dis.dis(compile('1000 is 1000', '<stdin>', 'eval'))
1 0 LOAD_CONST 0 (1000)
3 LOAD_CONST 0 (1000)
6 COMPARE_OP 8 (is)
9 RETURN_VALUE
这里LOAD_CONST
指的是索引 0 处的常数;.co_consts
您可以在字节码对象的属性中看到存储的常量。
将此与1000 is 10 ** 3
案例进行比较:
>>> compile('1000 is 10**3', '<stdin>', 'eval').co_consts
(1000, 10, 3, 1000)
>>> dis.dis(compile('1000 is 10**3', '<stdin>', 'eval'))
1 0 LOAD_CONST 0 (1000)
3 LOAD_CONST 3 (1000)
6 COMPARE_OP 8 (is)
9 RETURN_VALUE
有一个窥孔优化,可以在编译时预先计算常量上的表达式,并且这种优化已替换10 ** 3
为1000
,但该优化不会重用预先存在的常量。结果,LOAD_CONST
操作码在索引 0 和 3 处加载了两个不同的整数对象,它们是两个不同 int
的对象。
然后在小整数被实习的地方进行了优化;1
在 Python 程序的生命周期内只创建了一个对象的副本;这适用于 -5 到 256 之间的所有整数。
因此,对于这种1 is 1**2
情况,Python 内部使用int()
来自内部缓存的单例对象。这是一个 CPython 实现细节。
这个故事的寓意是,is
当你真的想按价值进行比较时,永远不要使用。始终用于==
整数。