'is'运算符与浮点数意外地表现

时间:2016-08-08 16:59:39

标签: python python-2.7 python-3.x floating-point python-internals

单元测试模块时遇到了一个令人困惑的问题。该模块实际上是铸造值,我想比较这些值。

==is相比有所不同(部分地,我要注意区别)

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

正如预期到现在为止,但这是我的“问题”:

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

为什么呢?至少最后一个对我来说真的很困惑。 float(0)float(0.0)的内部表示应该相等。与==的比较正如预期的那样。

2 个答案:

答案 0 :(得分:21)

这与is的工作方式有关。它检查引用而不是值。如果将任一参数分配给同一对象,则返回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(),则只返回浮点文字(短路),就像使用相同的引用一样,因为没有需要从现有的float中实例化一个新的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的结果也取决于它的执行范围(超出此问题/解释的范围),请参阅用户: @ Jim code objects的精彩解释。即使是python的doc也包含有关此行​​为的部分:

  

<强> [7]   由于自动垃圾收集,空闲列表和描述符的动态特性,您可能会注意到is运算符的某些使用中看似异常的行为,如涉及实例方法或常量之间的比较。查看他们的文档以获取更多信息。

答案 1 :(得分:9)

如果向float提供float()对象,则 CPython *只返回它而不创建新对象。

这可以在PyNumber_Float(最终从float_new调用)中看到,其中传入的对象o使用PyFloat_CheckExact进行检查;如果True,它只会增加其引用计数并返回它:

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

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

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

缩减为:

>>> 0.0 is 0.0

但为什么这等于True?好吧,CPython有一些优化。

在这种情况下,它在命令中两次出现0.0时使用相同的对象,因为它们是the same code object的一部分(简短免责声明:它们位于同一逻辑行上);所以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对象并返回该对象。为此,它分别使用PyFloat_FromDoublePyFloat_FromString

效果是返回的对象在id s(用于检查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) 

*注意:前面提到的所有行为都适用于CCPython的python实现。其他实现可能表现出不同的行为简而言之,不依赖它