ove*_*nge 6 python hash dictionary
如这里提到的,
下面的代码,
class Person(object):
def __init__(self, name, ssn, address):
self.name = name
self.ssn = ssn
self.address = address
def __hash__(self):
print('in hash')
return hash(self.ssn)
def __eq__(self, other):
print('in eq')
return self.ssn == other.ssn
bob = Person('bob', '1111-222-333', None)
jim = Person('jim bo', '1111-222-333', 'sf bay area')
dmv_appointments = {}
print('calling hash')
dmv_appointments[bob] = 'tomorrow'
print('calling hash')
print(dmv_appointments[jim])
print('calling hash again')
print(dmv_appointments[bob])
Run Code Online (Sandbox Code Playgroud)
输出:
calling hash
in hash
calling hash
in hash
in eq
tomorrow
calling hash again
in hash
tomorrow
Run Code Online (Sandbox Code Playgroud)
题:
为什么__eq__要求访问jim而不是访问bob?
简短回答:字典查找首先x is y在搜索存储桶时执行(廉价)引用相等性检查(),并且只有在失败时才会执行(更昂贵的)等式检查(x == y).
该__hash__函数不会在 __eq__内部调用.鉴于你构造,bob并jim没有调用这样的方法.
接下来你联系bob了'tomorrow'.为了知道你必须存储的字典的哪个桶bob,你计算哈希值.现在,一旦你完成了我们存储bob(并将值存储在正确的存储桶中).
接下来我们想获得jim.为了知道哪个桶jim驻留,我们计算哈希值.接下来我们开始在桶中搜索.桶将包含bob.我们首先执行引用check(jim is bob)但是失败了,所以我们回退了相等性检查.该检查成功,因此我们返回与bob:对应的值'tomorrow'.
当我们想要查找时会发生相同的情况bob:我们计算哈希值,获取桶.执行基准检查上bob is bob,而一个成功.所以我们不需要(可能更昂贵的平等检查).我们只是返回值'tomorrow'.
首先完成引用检查的事实可以用以下(不健康的)代码证明:
class Person(object):
def __init__(self, name, ssn, address):
self.name = name
self.ssn = ssn
self.address = address
def __hash__(self):
print('in hash')
return hash(self.ssn)
def __eq__(self, other):
print('in eq')
return FalseRun Code Online (Sandbox Code Playgroud)
在这里,我们总是回归False平等.甚至:
>>> bob == bob
in eq
False
>>> bob is bob
True
Run Code Online (Sandbox Code Playgroud)
bob不等于它本身(这实际上并不是好的设计,因为对于字典而言,它是一个契约,它是一个对象与自身相等:良好的平等关系是反身的,对称的和可传递的).然而,如果我们联想bob用'tomorrow',我们依然能够取得与关联的值bob:
>>> dmv_appointments = {}
>>> dmv_appointments[bob] = 'tomorrow'
in hash
>>> dmv_appointments[bob]
in hash
'tomorrow'
Run Code Online (Sandbox Code Playgroud)
回答一下标题:
什么时候
__eq__使用 hash() 被调用?
绝不。
另一个问题:
为什么
__eq__访问 jim 时会被调用,但访问 bob 时不会被调用?
那更复杂了。要理解这一点,您需要知道字典是如何实现的。假设 CPython 是一个包含一hash列、一key列和一value列的表:
hash | key | value
-----------------------------------------
- | - | -
-----------------------------------------
- | - | -
Run Code Online (Sandbox Code Playgroud)
它有一定的大小,但不足以包含所有可能的hash值,因此它将根据hash. 例如,如果您添加bob它可能具有(字符串哈希在某些 CPython 版本中是随机的,因此实际结果会有所不同)hasha 7475314405837642385。假设字典的实际大小为 2(实际上它会更大,但这会不必要地浪费答案中的空间),它只取模,因此它将把它放在7475314405837642385 % 2 == 1:
hash | key | value
-----------------------------------------
- | - | -
-----------------------------------------
747...385| bob | 'tomorrow'
Run Code Online (Sandbox Code Playgroud)
当你想查找某个键时
hash查找的值hash查找的值与hash该位置保存的值进行比较hashes 相等,那么它将比较查找和保存的key内容PyObject_RichCompareBool。这将:
lookup is keyFalse它将检查lookup == key所以如果你查找bob:
74753144058376423857475314405837642385 % 2->1hashes:7475314405837642385 == 7475314405837642385bob is bob->True所以它返回时'tomorrow'没有进行相等性检查。在第二种情况下,它检查jim:
74753144058376423857475314405837642385 % 2->1hashes:7475314405837642385 == 7475314405837642385jim is bob->Falsejim == bob->True所以它返回了'tomorrow'。
这只是实际实现的近似值(缺少一些细节)。如果hashes 不相等或者lookup is not key and lookup != key情况会变得更加复杂,但这些对于理解您所质疑的观察到的行为并不重要。
然而,我真的需要这么说:你所做的事情真的很危险,因为你的类不是一成不变的。您可能会意外地使保存的字典条目不可用:
dmv_appointments = {bob: 1}
bob.ssn = '1' # changing ssn changes the hash!
dmv_appointments[bob]
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
<ipython-input-35-3920ada7bab1> in <module>()
15 dmv_appointments = {bob: 1}
16 bob.ssn = '1'
---> 17 dmv_appointments[bob]
KeyError: <__main__.Person object at 0x000001BD5DDCC470>
Run Code Online (Sandbox Code Playgroud)
(如果新的hash等于“旧”哈希,它仍然可以工作,但这将是相当偶然的)。
这是因为当您更改hash实例的时 - 字典不会更新保存的hash,因为它假设所有键都是不可变的!因此,字典要么假设它将保存在另一个位置,要么如果该位置(奇迹般地)相同,那么它会在包含实际哈希值的步骤中失败。
| 归档时间: |
|
| 查看次数: |
382 次 |
| 最近记录: |