为什么浮点字典键会覆盖具有相同值的整数键?

sjd*_*nny 56 floating-point int dictionary python-2.7

我正在通过http://www.mypythonquiz.com工作,问题#45要求输出以下代码:

confusion = {}
confusion[1] = 1
confusion['1'] = 2
confusion[1.0] = 4

sum = 0
for k in confusion:
    sum += confusion[k]

print sum
Run Code Online (Sandbox Code Playgroud)

输出是6,因为密钥1.0替换1.这对我来说有点危险,这是一个有用的语言功能吗?

Bak*_*riu 103

首先:在散列函数的文档中明确记录了行为:

hash(object)

返回对象的哈希值(如果有的话).哈希值是整数.它们用于在字典查找期间快速比较字典键.该比较相等的数字值具有相同的散列值(即使它们是不同的类型,如对于壳体1 和1.0).

其次,在文档中指出了哈希的局限性 object.__hash__

object.__hash__(self)

通过内置的函数调用hash()和操作杂乱的集合成员,包括set,frozenset和dict. __hash__() 应该返回一个整数.唯一需要的属性是比较相等的对象具有相同的哈希值;

这不是python独有的.Java也有同样的警告:如果你实现hashCode那么,为了使事情正常工作,你必须以这样的方式实现它:x.equals(y)implies x.hashCode() == y.hashCode().

所以,蟒蛇决定1.0 == 1成立,因此它不得不提供一个实现hash这样hash(1.0) == hash(1).副作用是1.0与键1完全相同dict的行为,因此行为.

换句话说,行为本身不必以任何方式使用或使用.这是必要的.如果没有这种行为,可能会出现意外覆盖不同密钥的情况.

如果我们有,1.0 == 1但hash(1.0) != hash(1)我们仍然可以发生碰撞.如果1.0和1碰撞,dict将使用相等来确定它们是否是相同的密钥和kaboom,即使您打算将它们变为不同,该值也会被覆盖.

避免这种情况的唯一方法就是拥有1.0 != 1,以便dict即使在碰撞的情况下也能够区分它们.但是被认为1.0 == 1比避免你所看到的行为更重要,因为你几乎从不使用floats和ints作为字典键.

由于python试图通过在需要时自动转换它们来隐藏数字之间的区别(例如1/2 -> 0.5),即使在这种情况下也能反映出这种行为.它与python的其余部分更加一致.


此行为将出现在任何实现中,其中键的匹配至少部分(如在哈希映射中)基于比较.

例如,如果a dict是使用红黑树或其他类型的平衡BST实现的,当1.0查找键时,与其他键的比较将返回与之相同的结果1,因此它们仍将以相同的方式起作用.

哈希映射需要更加小心,因为它是用于查找键的条目的哈希值,并且仅在之后进行比较.因此,破坏上面提到的规则意味着你会引入一个很难发现的错误,因为有时dict可能看起来像你期望的那样工作,而在其他时候,当尺寸改变时,它会开始表现不正确.


请注意,将是解决这一问题的方式:对每种类型插在字典中的单独的散图/ BST.通过这种方式,不同类型的对象之间不会发生任何冲突,并且==当参数具有不同类型时,比较无关紧要.

然而,这会使实现复杂化,因为哈希映射必须保留相当多的空闲位置以便具有O(1)访问时间,所以它可能是低效的.如果它们变得太满,性能会下降.拥有多个哈希映射意味着浪费更多空间,而且在开始实际查找密钥之前,您还需要首先选择要查看的哈希映射.

如果您使用BST,则首先必须查找类型并执行第二次查找.因此,如果您要使用多种类型,您最终会得到两倍的工作(并且查找将采用O(log n)而不是O(1)).

  • 我认为这解释了事情的发展 (2认同)
  • 我发现关于散列的讨论完全无关紧要。这是一个实现细节,一个为每个值返回“42”的哈希函数将是一个有效但效率低下的哈希(因此您永远无法根据哈希值决定任何事情)。关键点是在 Python 中“3 == 3.0”并且该字典在相等上起作用。 (2认同)

650*_*502 18

您应该考虑dict根据逻辑数值存储数据的目的,而不是您如何表示它.

ints和floats 之间的区别确实只是一个实现细节而不是概念.理想情况下,唯一的数字类型应该是一个任意精度数,具有无限精度甚至是亚统一...但是这很难实现而不会遇到麻烦...但可能是Python将来唯一的数字类型.

因此,尽管出于技术原因而有不同的类型,Python会尝试隐藏这些实现细节,并且int- > float转换是自动的.

如果在一个Python程序这将是更更令人惊讶的if x == 1: ...是不会时要采取x一个float值为1.

请注意,还与Python 3的值1/2就是0.5(两个整数的除法),而且类型long和非Unicode字符串已被丢弃具有相同试图隐藏实现细节.

  • *"整数和浮点数之间的区别实际上只是一个实现细节,而不是概念性的."*我不同意(尽管对于整数和长期来说都是如此).Ints和float有明显不同的除法行为(正如你所注意到的),只有int提供`.bit_length()`方法.浮点数也不允许用作数组索引 - 如果它们应该是它们应该实现`__index__`并且仅针对非整数值引发错误.这些肯定是概念上的差异,而不仅仅是实现差异. (3认同)
  • `int-> float` promotion*应该在必要的上下文中自动生成,但在这种情况下,我认为它不是.当你试图将`int`放入一个超出`float`范围的字典或者不能往返的字典时会发生什么? (2认同)
  • @leftaroundabout:如果你喜欢`int`和`float`是不同的类型那么你不应该对`3 == 3.0`感到满意; 但这对IMO来说非常烦人(即使OCaml家伙的想法不同).如果`3 == 3.0`那么`x [3]`也应该与`x [3.0]`相同.另一方面,`3.0000000001`是不同的东西,它引发错误可能有助于调试问题.顺便说一句,双精度数字可以完全代表**所有整数,绝对值小于2 ^ 53 ...即**9,007,199,254,740,992**(我们不会有相当长的阵列很长一段时间). (2认同)

Uri*_*ren 7

在python中:

1==1.0
True
Run Code Online (Sandbox Code Playgroud)

这是因为隐式转换

然而:

1 is 1.0
False
Run Code Online (Sandbox Code Playgroud)

我能看到为什么之间自动注入float和int简便,这是比较安全的投int入float,然而也有其他语言(如去)是远离隐式转换了.

它实际上是一种语言设计决定,而不仅仅是不同的功能

  • 带有数字的`是`不是一个好主意......例如在`x = 1000000`之后,表达式`x是1000000`是'False`. (13认同)

Mar*_*som 6

字典使用哈希表实现.要在哈希表中查找某些内容,请从哈希值指示的位置开始,然后搜索不同的位置,直到找到相等的键值或空桶.

如果您有两个比较相等但具有不同哈希值的键值,则可能会得到不一致的结果,具体取决于其他键值是否在搜索位置中.例如,当表格变满时,这种情况更有可能发生.这是你想要避免的.似乎Python开发人员考虑到了这一点,因为内置hash函数返回相同数字值的相同哈希值,无论这些值是否为int或float.请注意,这扩展到其他数字类型,False等于0和True等于1.甚至fractions.Fraction并decimal.Decimal坚持这个财产.

如果要求a == b则hash(a) == hash(b)在定义中记载object.__hash__():

通过内置的函数调用hash()和杂乱的集合的成员包括运营set,frozenset和dict.__hash__()应该返回一个整数.唯一需要的属性是比较相等的对象具有相同的哈希值; 建议以某种方式将对象的组件的哈希值混合在一起(例如,使用异或),这些哈希值也在对象的比较中起作用.

TL; DR:如果比较相等的键没有映射到相同的值,则字典会中断.