我正在通过http://www.mypythonquiz.com,而question #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
。这对我来说有点危险,这是一个有用的语言功能吗?
答案 0 :(得分:96)
首先:行为在hash函数的文档中明确记录:
<强>
hash(object)
强>返回对象的哈希值(如果有)。哈希值是 整数。它们用于在a期间快速比较字典键 字典查找。 比较相等的数字值具有相同的值 哈希值(即使它们的类型不同,就像
1
的情况一样 和1.0
)。
其次,在object.__hash__
<强>
object.__hash__(self)
强>由内置函数
hash()
调用,以及对成员的操作 哈希集合包括set
,frozenset
和dict. __hash__()
应该返回一个整数。 唯一需要的属性是对象 比较相等的具有相同的哈希值;
这不是python独有的。 Java也有同样的警告:如果你实现了hashCode
,那么为了让事情正常工作,你必须以这样的方式实现它:x.equals(y)
暗示{{1 }}
因此,python决定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
比避免你看到的行为更重要,因为你几乎从不使用float
和int
作为字典键。
由于python试图通过在需要时自动转换数字来隐藏数字之间的区别(例如1/2 -> 0.5
),因此即使在这种情况下也会反映出这种行为。它与python的其余部分更加一致。
此行为将出现在任何实现中,其中键的匹配至少部分(如在哈希映射中)基于比较。
例如,如果使用红黑树或其他类型的平衡BST实现dict
,则在查找键1.0
时,与其他键的比较将返回与对于1
,他们仍然会以同样的方式行事。
哈希映射需要更加谨慎,因为它是用于查找密钥条目的哈希的值,并且仅在之后进行比较。因此违反上述规则意味着您要引入一个很难发现的错误,因为dict
有时可能会像您期望的那样工作,而在其他时候,当尺寸改变时,它将开始表现不正确。
请注意,将成为解决此问题的方法:为字典中插入的每种类型设置单独的哈希映射/ BST。通过这种方式,不同类型的对象之间不会发生任何冲突,并且当参数具有不同类型时,==
比较的方式并不重要。
然而,这会使实现变得复杂,它可能效率低下,因为哈希映射必须保留相当多的空闲位置才能具有O(1)访问时间。如果它们变得太满,性能会下降。拥有多个哈希映射意味着浪费更多空间,而且在开始实际查找密钥之前,您还需要首先选择要查看的哈希映射。
如果您使用BST,则首先必须查找类型并执行第二次查找。因此,如果您要使用多种类型,您最终会得到两倍的工作(并且查找将采用O(log n)而不是O(1))。
答案 1 :(得分:16)
您应该考虑dict
的目的是根据逻辑数值存储数据,而不是根据您的表示方式存储数据。
int
和float
之间的差异确实只是一个实现细节,而不是概念性的。理想情况下,唯一的数字类型应该是一个任意精度数,具有无限精度甚至是亚统一...但是这很难实现而不会遇到麻烦......但可能是Python未来唯一的数字类型。
因此,由于技术原因,Python会尝试隐藏这些实现细节,而int
- &gt; float
转换是自动的。
如果在if x == 1: ...
x
为float
且价值为1的情况下,1/2
不会被采用,那将更令人惊讶。
请注意,对于Python 3,0.5
的值为long
(两个整数的除法),并且类型git remote add openshift -f <openshift-git-repo-url
(ssh://...something)>
和非unicode字符串已被删除试图隐藏实施细节。
答案 2 :(得分:7)
在python中:
1==1.0
True
这是因为隐式转换
然而:
1 is 1.0
False
我可以看到为什么float
和int
之间的自动投射很方便,将int
投射到float
是相对安全的,但还有其他语言(例如go)远离隐性铸造。
这实际上是一种语言设计决定,而不是不同功能的品味问题
答案 3 :(得分:6)
字典使用哈希表实现。要在哈希表中查找某些内容,请从哈希值指示的位置开始,然后搜索不同的位置,直到找到相等的键值或空桶为止。
如果您有两个比较相等且具有不同哈希值的键值,则可能会得到不一致的结果,具体取决于其他键值是否在搜索位置中。例如,当表格变满时,这种情况更有可能发生。这是你想要避免的。似乎Python开发人员考虑到了这一点,因为内置的hash
函数返回相同数字值的相同哈希值,无论这些值是int
还是float
。请注意,这扩展到其他数字类型,False
等于0
,True
等于1
。即使fractions.Fraction
和decimal.Decimal
也支持此属性。
在object.__hash__()
的定义中记录a == b
然后hash(a) == hash(b)
的要求:
由内置函数
hash()
调用,以及对散列集合成员的操作,包括set
,frozenset
和dict
。__hash__()
应该返回一个整数。唯一需要的属性是比较相等的对象具有相同的哈希值;建议以某种方式将对象组件的哈希值混合在一起(例如使用exclusive或),这些哈希值也是对象比较中的一部分。
TL; DR:如果比较相等的键没有映射到相同的值,则字典会中断。
答案 4 :(得分:3)
1 == 1.0
,所以想象如果你让他们指向不同的密钥并尝试根据评估的数字访问它们并不是不可能的,那么你可能会遇到麻烦,因为模糊不清很难搞清楚。
动态类型意味着该值比某些技术类型更重要,因为该类型具有可塑性( 是一个非常有用的特性),因此区分ints
与不同的值相同的floats
是不必要的语义,只会导致混淆。
答案 5 :(得分:3)
我同意其他人认为在这种情况下将1
和1.0
视为相同是有意义的。即使Python确实以不同方式对待它们,尝试使用1
和1.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
所以我会说,一般来说,你的问题的答案“这是一个有用的语言功能吗?”是“不,可能不是。”