'is' 运算符对浮点数的行为异常

'is' operator behaves unexpectedly with floats

我在对模块进行单元测试时遇到了一个令人困惑的问题。该模块实际上正在转换值,我想比较这些值。

==is相比有差异(部分,我提防差异)

>>> 0.0 is 0.0
True   # as expected
>>> float(0.0) is 0.0
True   # as expected

正如预期的那样,但这是我的 "problem":

>>> float(0) is 0.0
False
>>> float(0) is float(0)
False

为什么?至少最后一个真的让我感到困惑。 float(0)float(0.0) 的内部表示应该相等。与 == 的比较按预期工作。

这与 is 的工作原理有关。它检查引用而不是值。它 returns True 如果任一参数被分配给同一个对象。

在这种情况下,它们是不同的实例; float(0)float(0) 具有相同的值 ==,但就 Python 而言是不同的实体。 CPython 实现还将整数缓存为该范围内的单例对象 -> [x | x ∈ ℤ ∧ -5 ≤ x ≤ 256 ]:

>>> 0.0 is 0.0
True
>>> float(0) is float(0)  # Not the same reference, unique instances.
False

在这个例子中我们可以演示整数缓存原理:

>>> a = 256
>>> b = 256
>>> a is b
True
>>> a = 257
>>> b = 257
>>> a is b
False

现在,如果将浮点数传递给 float(),则只返回浮点数(短路),因为使用了相同的引用,因为有无需从现有浮点数实例化新浮点数:

>>> 0.0 is 0.0
True
>>> float(0.0) is float(0.0)
True

这可以通过使用 int() 进一步证明:

>>> int(256.0) is int(256.0)  # Same reference, cached.
True
>>> int(257.0) is int(257.0)  # Different references are returned, not cached.
False
>>> 257 is 257  # Same reference.
True
>>> 257.0 is 257.0  # Same reference. As @Martijn Pieters pointed out.
True

但是,is的结果也取决于它正在执行的范围(超出了这个question/explanation的范围),请参考用户:@Jim's fantastic explanation on 。甚至 python 的文档也包含有关此行​​为的部分:

[7] Due to automatic garbage-collection, free lists, and the dynamic nature of descriptors, you may notice seemingly unusual behaviour in certain uses of the is operator, like those involving comparisons between instance methods, or constants. Check their documentation for more info.

如果将 float 对象提供给 float()CPython* 只需 return 即可,无需创建新对象。

这可以在PyNumber_Float (which is eventually called from float_new) where the object o passed in is checked with PyFloat_CheckExact中看到;如果 True,它只是增加它的引用计数并且 returns 它:

if (PyFloat_CheckExact(o)) {
    Py_INCREF(o);
    return o;
}

因此,对象的 id 保持不变。所以表达式

>>> float(0.0) is float(0.0) 

减少为:

>>> 0.0 is 0.0

但为什么等于 True?嗯,CPython 有一些 small 优化。

在这种情况下,它对您命令中两次出现的 0.0 使用相同的对象,因为它们是 的一部分(简短免责声明:它们在同一逻辑行上) ;所以 is 测试会成功。

如果您在单独的行中执行 float(0.0)(或由 ; 分隔)并 然后 检查身份,则可以进一步证实这一点:

a = float(0.0); b = float(0.0) # Python compiles these separately
a is b # False 

另一方面,如果提供 int(或 str),CPython 将创建一个 new float 对象从它和 return 那。为此,它分别使用 PyFloat_FromDouble and PyFloat_FromString

效果是 returned 对象在 ids 中不同(用于检查与 is 的身份):

# Python uses the same object representing 0 to the calls to float
# but float returns new float objects when supplied with ints
# Thereby, the result will be False
float(0) is float(0) 

*注意: 所有前面提到的行为都适用于 C 中 python 的实现,即 CPython。其他实现可能会表现出不同的行为。总之,不要依赖.