为什么浮点字典键可以覆盖具有相同值的整数键?
Why can a floating point dictionary key overwrite an integer key with the same value?
我正在处理 http://www.mypythonquiz.com, and 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
。这对我来说有点危险,这是一个有用的语言功能吗?
老实说,对面很危险! 1 == 1.0
,所以可以想象,如果你让它们指向不同的键并尝试根据评估的数字访问它们,那么你可能 运行 会遇到麻烦,因为歧义很难弄清楚。
动态类型意味着值比事物的技术类型更重要,因为类型是可塑的(是一个非常有用的特性),所以区分两者ints
和 floats
具有与 distinct 相同的值是不必要的语义,只会导致混淆。
在python中:
1==1.0
True
这是因为隐式转换
但是:
1 is 1.0
False
我明白为什么 float
和 int
之间的自动转换很方便,将 int
转换为 float
相对安全,但还有其他语言(例如 go) 远离隐式转换。
这实际上是一个语言设计决定,比不同的功能更重要的是品味问题
你应该考虑到 dict
旨在根据逻辑数值存储数据,而不是你如何表示它。
int
s 和 float
s 之间的区别确实只是一个实现细节,而不是概念上的。理想情况下,唯一的数字类型应该是一个任意精度的数字,具有无限的精度,甚至是亚单位......然而,这很难在不遇到麻烦的情况下实现......但可能这将是 [=31= 的唯一未来数字类型].
因此,虽然由于技术原因具有不同的类型,Python 试图隐藏这些实现细节,并且 int
->float
转换是自动的。
如果在 Python 程序中当 x
是一个值为 1 的 float
时 if x == 1: ...
不会被采用,那就更令人惊讶了。
请注意,对于 Python 3,1/2
的值也是 0.5
(两个整数相除)并且类型 long
和非 unicode 字符串已因隐藏实施细节的相同尝试而被删除。
我同意其他人的观点,在这种情况下将 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
所以我想说,一般来说,你的问题 "is this ever a useful language feature?" 的答案是 "No, probably not."
首先:hash 函数的文档中明确记录了该行为:
hash(object)
Return the hash value of the object (if it has one). Hash values are
integers. They are used to quickly compare dictionary keys during a
dictionary lookup. Numeric values that compare equal have the same
hash value (even if they are of different types, as is the case for 1
and 1.0
).
的文档中指出了散列的局限性
object.__hash__(self)
Called by built-in function hash()
and for operations on members of
hashed collections including set
, frozenset
, and dict. __hash__()
should return an integer. The only required property is that objects
which compare equal have the same hash value;
这不是 python 独有的。 Java 有同样的警告:如果你实现 hashCode
那么,为了让事情正常工作,你 必须 以这样的方式实现它: x.equals(y)
表示 x.hashCode() == y.hashCode()
.
因此,python 决定 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
s 和 int
s 作为字典键。
由于 python 试图通过在需要时自动转换它们来隐藏数字之间的区别(例如 1/2 -> 0.5
),因此即使在这种情况下也会反映出这种行为是有道理的。它与 python.
的其余部分更加一致
此行为会出现在 任何 实现中,其中键的匹配至少部分(如在哈希映射中)基于比较。
例如,如果 dict
是使用红黑树或其他类型的平衡 BST 实现的,当查找键 1.0
时,与其他键的比较将 return 与 1
相同的结果,因此它们仍将以相同的方式运行。
哈希映射需要更加小心,因为它是用于查找键条目的哈希值,并且只有在之后才进行比较。因此,违反上述规则意味着您会引入一个很难发现的错误,因为有时 dict
可能看起来像您期望的那样工作,而在其他时候,当大小发生变化时,它会开始表现不正确。
请注意, 可以解决此问题:为字典中插入的每种类型设置单独的散列 map/BST。这样,不同类型的对象之间就不会发生任何冲突,当参数具有不同类型时,==
比较的方式也无关紧要。
然而,这会使实现复杂化,它可能效率低下,因为哈希映射必须保留相当多的空闲位置才能获得 O(1) 访问时间。如果它们变得太满,性能就会下降。拥有多个哈希映射意味着浪费更多 space 而且您甚至需要在开始实际查找密钥之前先选择要查看的哈希映射。
如果您使用 BST,您首先必须查找类型并执行第二次查找。因此,如果您要使用许多类型,您最终会完成两倍的工作(并且查找将花费 O(log n) 而不是 O(1))。
字典是用散列 table 实现的。要在散列 table 中查找内容,您从散列值指示的位置开始,然后搜索不同的位置,直到找到一个相等的键值或一个空桶。
如果您有两个比较相等但哈希值不同的键值,您可能会得到不一致的结果,具体取决于另一个键值是否在搜索位置。例如,当 table 变满时,这种情况更有可能发生。这是您要避免的事情。 Python 开发人员似乎考虑到了这一点,因为内置 hash
函数 returns 相同的哈希值用于等效数值,无论这些值是否 int
或 float
。请注意,这扩展到其他数字类型,False
等于 0
,True
等于 1
。就连fractions.Fraction
和decimal.Decimal
也坚持这个属性.
if a == b
then hash(a) == hash(b)
的要求记录在 object.__hash__()
的定义中:
Called by built-in function hash()
and for operations on members of hashed collections including set
, frozenset
, and dict
. __hash__()
should return an integer. The only required property is that objects which compare equal have the same hash value; it is advised to somehow mix together (e.g. using exclusive or) the hash values for the components of the object that also play a part in comparison of objects.
TL;DR: 如果比较相等的键没有映射到相同的值,字典就会崩溃。
我正在处理 http://www.mypythonquiz.com, and 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
。这对我来说有点危险,这是一个有用的语言功能吗?
老实说,对面很危险! 1 == 1.0
,所以可以想象,如果你让它们指向不同的键并尝试根据评估的数字访问它们,那么你可能 运行 会遇到麻烦,因为歧义很难弄清楚。
动态类型意味着值比事物的技术类型更重要,因为类型是可塑的(是一个非常有用的特性),所以区分两者ints
和 floats
具有与 distinct 相同的值是不必要的语义,只会导致混淆。
在python中:
1==1.0
True
这是因为隐式转换
但是:
1 is 1.0
False
我明白为什么 float
和 int
之间的自动转换很方便,将 int
转换为 float
相对安全,但还有其他语言(例如 go) 远离隐式转换。
这实际上是一个语言设计决定,比不同的功能更重要的是品味问题
你应该考虑到 dict
旨在根据逻辑数值存储数据,而不是你如何表示它。
int
s 和 float
s 之间的区别确实只是一个实现细节,而不是概念上的。理想情况下,唯一的数字类型应该是一个任意精度的数字,具有无限的精度,甚至是亚单位......然而,这很难在不遇到麻烦的情况下实现......但可能这将是 [=31= 的唯一未来数字类型].
因此,虽然由于技术原因具有不同的类型,Python 试图隐藏这些实现细节,并且 int
->float
转换是自动的。
如果在 Python 程序中当 x
是一个值为 1 的 float
时 if x == 1: ...
不会被采用,那就更令人惊讶了。
请注意,对于 Python 3,1/2
的值也是 0.5
(两个整数相除)并且类型 long
和非 unicode 字符串已因隐藏实施细节的相同尝试而被删除。
我同意其他人的观点,在这种情况下将 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
所以我想说,一般来说,你的问题 "is this ever a useful language feature?" 的答案是 "No, probably not."
首先:hash 函数的文档中明确记录了该行为:
的文档中指出了散列的局限性
hash(object)
Return the hash value of the object (if it has one). Hash values are integers. They are used to quickly compare dictionary keys during a dictionary lookup. Numeric values that compare equal have the same hash value (even if they are of different types, as is the case for
1
and1.0
).
object.__hash__(self)
Called by built-in function
hash()
and for operations on members of hashed collections includingset
,frozenset
, anddict. __hash__()
should return an integer. The only required property is that objects which compare equal have the same hash value;
这不是 python 独有的。 Java 有同样的警告:如果你实现 hashCode
那么,为了让事情正常工作,你 必须 以这样的方式实现它: x.equals(y)
表示 x.hashCode() == y.hashCode()
.
因此,python 决定 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
s 和 int
s 作为字典键。
由于 python 试图通过在需要时自动转换它们来隐藏数字之间的区别(例如 1/2 -> 0.5
),因此即使在这种情况下也会反映出这种行为是有道理的。它与 python.
此行为会出现在 任何 实现中,其中键的匹配至少部分(如在哈希映射中)基于比较。
例如,如果 dict
是使用红黑树或其他类型的平衡 BST 实现的,当查找键 1.0
时,与其他键的比较将 return 与 1
相同的结果,因此它们仍将以相同的方式运行。
哈希映射需要更加小心,因为它是用于查找键条目的哈希值,并且只有在之后才进行比较。因此,违反上述规则意味着您会引入一个很难发现的错误,因为有时 dict
可能看起来像您期望的那样工作,而在其他时候,当大小发生变化时,它会开始表现不正确。
请注意, 可以解决此问题:为字典中插入的每种类型设置单独的散列 map/BST。这样,不同类型的对象之间就不会发生任何冲突,当参数具有不同类型时,==
比较的方式也无关紧要。
然而,这会使实现复杂化,它可能效率低下,因为哈希映射必须保留相当多的空闲位置才能获得 O(1) 访问时间。如果它们变得太满,性能就会下降。拥有多个哈希映射意味着浪费更多 space 而且您甚至需要在开始实际查找密钥之前先选择要查看的哈希映射。
如果您使用 BST,您首先必须查找类型并执行第二次查找。因此,如果您要使用许多类型,您最终会完成两倍的工作(并且查找将花费 O(log n) 而不是 O(1))。
字典是用散列 table 实现的。要在散列 table 中查找内容,您从散列值指示的位置开始,然后搜索不同的位置,直到找到一个相等的键值或一个空桶。
如果您有两个比较相等但哈希值不同的键值,您可能会得到不一致的结果,具体取决于另一个键值是否在搜索位置。例如,当 table 变满时,这种情况更有可能发生。这是您要避免的事情。 Python 开发人员似乎考虑到了这一点,因为内置 hash
函数 returns 相同的哈希值用于等效数值,无论这些值是否 int
或 float
。请注意,这扩展到其他数字类型,False
等于 0
,True
等于 1
。就连fractions.Fraction
和decimal.Decimal
也坚持这个属性.
if a == b
then hash(a) == hash(b)
的要求记录在 object.__hash__()
的定义中:
Called by built-in function
hash()
and for operations on members of hashed collections includingset
,frozenset
, anddict
.__hash__()
should return an integer. The only required property is that objects which compare equal have the same hash value; it is advised to somehow mix together (e.g. using exclusive or) the hash values for the components of the object that also play a part in comparison of objects.
TL;DR: 如果比较相等的键没有映射到相同的值,字典就会崩溃。