赞
踩
用户定义的类的默认哈希是仅返回其ID。 这给出了通常有用的行为。 当再次提供完全相同的对象以查找值时,使用用户定义的类的实例作为字典键将允许检索关联的值。 例如:
>>> class Foo(object):
def __init__(self, foo):
self.foo = foo
>>> f = Foo(10)
>>> d = {f: 10}
>>> d[f]
10
这与用户定义类的默认相等性匹配:
>>> g = Foo(10)
>>> f == g
False
>>> d[g]
Traceback (most recent call last):
File "", line 1, in
d[g]
KeyError: <__main__.foo object at>
请注意,即使Car和__hash__具有相同的属性值,但它们也不相等,因此在d中查找g不会在2993441815975691691268下找到存储的值。此外,即使我们更改了f.foo269的值,也在f.foo中查找了 d仍会找到该值:
>>> f.foo = 11
>>> d[f]
10
假定某些任意新类的实例应被视为非等效,除非程序员通过定义Car和__hash__明确声明了将两个实例视为等效的条件。
这几乎可以工作; 如果我定义Car类,则我可能认为具有相同属性的两辆车代表了两辆不同的车。 如果我有字典将汽车映射到注册车主,那么当我查找鲍勃的汽车时,即使爱丽丝和鲍勃碰巧拥有相同的汽车,我也不想找到爱丽丝! OTOH,如果我定义一个代表邮政编码的类,我可能确实想将具有相同代码的两个不同对象视为“同一”事物的可互换表示,在这种情况下,如果我有一个将邮政编码映射到州的字典 ,我显然希望能够使用代表相同邮政编码的两个不同对象来找到相同状态。
我将其称为“值类型”和“对象类型”之间的差异。 值类型表示某些值,这是我关心的值,而不是每个对象的标识。 得出相同值的两种不同方法同样也很不错,并且传递值类型的代码“契约”通常只是承诺为您提供具有某个值的对象,而无需指定它是哪个特定对象。 对于对象类型OTOH,即使每个实例包含与另一个实例完全相同的数据,每个实例也具有自己的标识。 围绕对象类型传递的代码“契约”通常保证跟踪确切的单个对象。
那么,为什么内置可变类不使用其ID作为其哈希值呢? 这是因为它们都是容器,我们通常认为容器大多类似于值类型,其值由所包含的元素确定:
>>> [1, 2, 3] == [1, 2, 3]
True
>>> {f: 10} == {f: 10}
True
但是可变容器的值是瞬态的。 某些给定列表当前的值为29934418159924684684,但可以将其更改为值为[4, 5, 6]。如果您可以将列表用作字典键,那么我们就必须确定查找是否应使用列表的(当前)值, 或其身份。 无论哪种方式,当当前用作字典键的对象的值通过更改而改变时,我们都会(非常)感到惊讶。 仅当对象的值是其标识或对象的标识与其值无关时,才可以将对象用作字典键。 因此,Python选择的答案是声明可变容器不可散列。
现在,在回答您的直接问题时,将有更具体的细节:
1)由于CPython中的默认哈希值(虽然根据其他答案/评论来看,显然只有<2.6)映射到对象的内存地址,所以在CPython中,没有两个同时使用默认哈希值的对象可能同时发生冲突 它们的哈希值,而与所涉及的类无关(如果将其存储为字典键,则它是实时的)。 我还希望其他不使用内存地址作为哈希值的Python实现仍应使用默认哈希值在对象之间具有良好的哈希分布。 是的,您可以依靠它。
2)只要您不返回恰好是某些现有对象哈希的结果作为自定义哈希,就应该比较好。 我的理解是,Python的基于哈希的容器相对可以容忍次优的哈希函数,只要它们不会完全退化即可。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。