为什么浮点字典键可以用相同的值覆盖整数键?

我正在通过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 

输出是6 ,因为键1.0取代了1 。 这对我来说有点危险,这是否是一个有用的语言function?

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

int s和float s的区别实际上只是一个实现细节而不是概念。 理想情况下,唯一的数字types应该是一个任意的精确数字,具有无限的准确性,即使是子统一性…但是这样做很难实现而不会陷入困境……但是这可能是Python将来唯一的数字types。

所以,虽然有不同types的技术原因,Python试图隐藏这些实现细节,并且int – > float转换是自动的。

如果在Python程序中if x == 1: ...不会被采用,那么当x是一个值为1的float时,会更令人惊讶。

请注意,对于Python 3,1 1/2 2的值是0.5 (两个整数的除法),并且typeslong和非unicodestring已被放弃,同样尝试隐藏实现细节。

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

hash(object)

返回对象的散列值(如果有的话)。 哈希值是整数。 它们用于在字典查找期间快速比较字典键。 比较相等的数值具有相同的散列值(即使它们是不同的types,就像11.0 )。

其次,哈希的限制在object.__hash__哈希的文档中被指出

object.__hash__(self)

由内置函数hash()调用,并用于散列集合(包括setfrozensetdict. __hash__() hash()成员的操作dict. __hash__() dict. __hash__()应该返回一个整数。 唯一需要的属性是比较相等的对象具有相同的散列值;

这不是python独有的。 Java有相同的警告:如果你实现hashCode然后,为了正常工作,你必须以这样的方式实现它: x.equals(y)意味着x.hashCode() == y.hashCode()

所以,python决定1.0 == 1成立,因此它被迫提供一个hash(1.0) == hash(1) 。 副作用是1.01作用与dict键完全相同,因此是行为。

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

如果我们有1.0 == 1hash(1.0) != hash(1)我们仍然可能有碰撞 。 如果1.01碰撞, dict将使用相等性来确定它们是否是相同的密钥,并且即使您希望它们不同, kaboom的值也会被覆盖。

避免这种情况的唯一方法是让1.0 != 1 ,这样即使在碰撞的情况下, dict也能够区分它们。 但是1.0 == 1被认为比避免你所看到的行为更重要,因为你几乎从不使用float s和int s作为字典键。

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


这种行为将出现在任何实现中,其中键的匹配至less部分地(如在哈希映射中)基于比较。

例如,如果使用红黑树或其他平衡BST实现dict ,当查找键1.0时,与其他键的比较将返回与1相同的结果,因此它们仍将在相同办法。

哈希映射需要更多的关注,因为它是用来查找密钥条目的哈希值,只有在之后才进行比较。 因此,打破上面提到的规则意味着你会引入一个很难find的错误,因为有时dict可能会像你期望的那样工作,而在其他时候,当尺寸改变时,它会开始performance不正确。


请注意,有一种方法可以解决这个问题:对字典中插入的每种types都有单独的哈希映射/ BST。 通过这种方式,不同types的对象之间不会有任何冲突,当参数具有不同的types时, ==如何比较并不重要。

然而,这会使实现复杂化,因为哈希映射必须保留相当多的空闲位置才能有O(1)个访问时间,所以这可能是低效的。 如果他们太满了,表演会减less。 拥有多个哈希映射意味着浪费更多空间,并且在开始实际查找关键字之前,您还需要先select要查看的哈希映射。

如果您使用BST,您首先必须查找types并执行第二次查找。 所以如果你打算使用很多types的话,你最终会得到两倍的工作量(查找将会花费O(log n)而不是O(1))。

在python中:

 1==1.0 True 

这是因为隐式投射

然而:

 1 is 1.0 False 

我可以看到为什么floatint之间的自动转换是方便的,将int转换为float是相对安全的,但是还有其他语言(比如go)远离隐式转换。

这实际上是一个语言devise的决定,而不仅仅是不同的function

字典是用哈希表来实现的。 要在散列表中查找某些内容,请从散列值指定的位置开始,然后search不同的位置,直到find等于或空的存储区的键值。

如果您有两个比较相等的密钥值,但具有不同的哈希值,则可能会得到不一致的结果,具体取决于其他键值是否位于search位置。 例如,当表格变满时,这将更有可能。 这是你想要避免的。 看来,Python开发人员有这个想法,因为内置的hash函数返回相同的哈希等价数值,无论这些值是int还是float 。 请注意,这扩展到其他数字types, False等于0True等于1 。 即使fractions.Fractiondecimal.Decimal维护这个属性。

如果a == b那么hash(a) == hash(b)被logging在object.__hash__()的定义中。

由内置函数hash()调用,并用于散列集合(包括setfrozensetdict hash()成员的操作。 __hash__()应该返回一个整数。 唯一需要的属性是比较相等的对象具有相同的散列值; build议以某种方式混合在一起(例如使用排他或)散列值的对象的组成部分,也发挥作用的比较对象的一部分。

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

坦率地说,相反是危险的! 1 == 1.0 ,所以想象一下,如果你让他们指向不同的键,并试图根据评估的数字来访问它们,那么你很可能会遇到麻烦,因为含糊不清是很难理解的。

dynamictypes意味着这个值比什么是技术types更重要,因为types是可延展的(这一个非常有用的特性),所以区分同一个值的intsfloats值是不同的,这是不必要的语义导致混乱。

我同意其他人的观点,认为在这种情况下, 11.0是一样的。 即使Python对待它们的方式不同,尝试使用11.0作为字典的不同键也是一个坏主意。 另一方面 – 我很难想到在键的上下文中使用1.0作为别名1的自然用例。 问题是关键是文字还是计算。 如果它是一个文字键,那么为什么不使用1而不是1.0呢? 如果这是一个计算密钥 – 四舍五入错误可能会弄脏事情:

 >>> d = {} >>> d[1] = 5 >>> d[1.0] 5 >>> x = sum(0.01 for i in range(100)) #conceptually this is 1.0 >>> d[x] Traceback (most recent call last): File "<pyshell#12>", line 1, in <module> d[x] KeyError: 1.0000000000000007 

所以我想说,一般来说,你的问题的答案是“这是否是一个有用的语言function?” 是“不,可能不是”。