【问题标题】:Implement custom keys for dictionary so that 2 instances of same class match为字典实现自定义键,以便相同类的 2 个实例匹配
【发布时间】:2015-04-23 00:49:19
【问题描述】:

我有 2 个类实例,我想将它们解析为字典中的相同键:

class CustomClass(): 
    def __hash__(self):
        return 2

a = CustomClass()        
b = CustomClass()        

dicty = {a : 1}

这里,a 和 b 不等于 key:

>>> a in dicty
True
>>> b in dicty
False

哈希到底发生了什么;似乎 CustomClass 的第二个实例应该与散列匹配?这些哈希不匹配是怎么回事?

我刚刚发现实际的类是被散列的。那么如何为类添加自定义字典键(即当我尝试使用类作为字典的键时,应该如何存储它以便 a 和 b 匹配)?

请注意,在这种情况下,我不关心在字典中保留指向原始对象的链接,我可以使用一些不可用的键对象;重要的是他们的决心相同。

编辑:

也许需要一些关于我想解决的实际案例的建议。

我的类包含形状为(8,6) 的布尔值np.arrays。我想对这些进行哈希处理,以便每当将此对象放入字典时,都会对这些值进行比较。我根据this 的回答用它们制作了一个位数组。我注意到那里有一个__cmp__(感谢thefourtheye 表明我必须看那里)。但是,我的课程可以更新,所以我只想在实际尝试将 np.array 放入字典时对其进行散列,而不是在初始化时(因此每当我 init 时存储可散列位数组,因为 np.array 可能会被更新,这样哈希就不再是真实的表示了)。我知道每当我更新 np.array 时,我也可以更新散列值,但我宁愿只散列一次!

【问题讨论】:

  • so I'd only like to hash the np.array when I'm actually trying to put it into a dictionary - 好吧,假设你存储在字典中。但如果后者可以改变,你将如何从字典中得到它?
  • 我不想从字典里得到它。
  • 那为什么还要把它存储在字典中呢?对我来说听起来像是XY Problem
  • 首先,我用初始散列类对象填充一个集合。之后,我动态生成了很多冗余的类对象,我想使用一个新的字典对其进行计数,只要有哈希命中,计数就会增加。在计数的情况下,我不关心类是否完全相等,我只关心那个阶段的哈希值。
  • 也许我不应该尝试 myset.add(myclassobject) 和测试 myclassobject in mydict 但我应该只做 myset.add(hash(myclassobject)hash(myclassobject) in mydict 只需要进行两次计算(如果我也会在__eq____cmp__ 中散列!

标签: python python-3.x dictionary hash


【解决方案1】:

你违反了__hash____cmp____eq__ 之间的合同。引用__hash__ documentation

如果一个类没有定义__cmp__()__eq__() 方法,它也不应该定义__hash__() 操作;如果它定义了__cmp__()__eq__() 但没有定义__hash__(),它的实例将不能在散列集合中使用。如果一个类定义了可变对象并实现了__cmp__()__eq__()方法,它不应该实现__hash__(),因为可哈希集合实现要求对象的哈希值是不可变的(如果对象的哈希值发生变化,它将在错误的哈希桶)。

用户自定义类默认有__cmp__()__hash__()方法;与它们相比,所有对象都比较不相等(除了它们自己)x.__hash__() 返回一个适当的值,使得x == y 暗示x is yhash(x) == hash(y)

在您的情况下,两个对象的哈希值相同,hash Collision 在任何哈希实现中都很常见。因此,Python 将正在查找的对象与帮助 __eq__ 方法进行比较,并发现正在搜索的实际对象与已存储的对象不同。这就是 b in dicty 返回 False 的原因。

所以,要解决您的问题,也可以像这样定义自定义 __eq__ 函数

class CustomClass():

    def __init__(self):
        self.data = <something>

    def __hash__(self):
        # Find hash value based on the `data`
        return hash(self.data)

    def __eq__(self, other):
        return self.data == other.data

注意:__hash__ 值对于给定对象应始终相同。因此,请确保data 在最初分配后永远不会更改。否则,您将永远无法从字典中获取对象,因为 datahash 值会有所不同,如果它在稍后的时间点发生变化。

【讨论】:

  • 我是否可以假设形状为 (8,6) 的 np.array(dtype=bool) 在其 262144 种可能状态中永远不会解析为相同的哈希,因此 __ eq __ 可能总是返回 False 不需要额外计算吗?
  • 我的__eq__ 基本上应该是hash(data) == hash(other.data),除非我觉得如果不需要重做那个测试是浪费?
  • 关于您的注意:每当我的对象的数据发生变化时,它就会在我的应用程序中变成不同的对象(并且不必匹配)
  • @PascalvKooten 嗯,不太清楚您要做什么。我感觉您只专注于在字典中获取对象,而不是担心将其取回。
【解决方案2】:

问题在于散列函数会导致冲突——不同的对象可以产生相同的散列值。因此,最终检查对象是否存在于 dict 中仍然使用相等比较(即x == y)。哈希值首先用于快速找到相关对象。

如果您想要您描述的行为,那么您还必须覆盖 __eq__

例如。

class CustomClass: 
    def __hash__(self):
        return 2
    def __eq__(self, other):
        return type(self) is type(other) and type(self) is CustomClass

【讨论】:

    【解决方案3】:

    __hash__ 只是确定将值放入哪个存储桶。在存储桶中,python 总是调用 __eq__ 以确保它不会返回恰好具有相同哈希但实际上不同的元素,因此您还需要实现自己的 __eq__

    class CustomClass():
        def __hash__(self):
            return 2
    
        def __eq__(self, other):
            return hash(other) == hash(self)
    
    
    a = CustomClass()     
    b = CustomClass()     
    
    dicty = {a : 1}
    
    print a in dicty
    print b in dicty
    print "a" in dicty
    

    【讨论】:

    • 这是否意味着如果我的哈希值不同,我可以输入return True?我实际的__hash__ 需要计算,我不想在__eq__ 阶段也这样做。实际上,这可能会让我突然间所有类的实例都会匹配?
    • 我添加了一个示例代码。仅输入 return True 是不够的,因为那样它也将等于其他类型的实例,我假设您不希望发生这种情况。
    • 啊,我想我现在明白你想要什么了。查看我更新的代码。只需让您的 __eq__ 函数比较哈希值,这样在字典中具有哈希冲突的元素实际上将被视为相等
    • @PascalvKooten 哈希对象必须是不可变的,因此您只需要计算一次哈希值。然后,您可以将哈希值缓存在 _hash 之类的属性中以供将来使用。
    • @Ishamael 但是,这需要额外的hash 计算,我试图避免。总是返回 False 怎么样?我的主要赌注是,如果有 262144 种可能的状态,哈希值永远不会匹配
    【解决方案4】:

    您应该实现__eq__ 方法来使您的对象hashablehashable的定义来自doc:

    一个对象是可散列的,如果它的散列值在运行期间永远不会改变 它的生命周期(它需要一个 __hash__() 方法),并且可以是 与其他对象相比(它需要一个 __eq__() 方法)。可散列 比较相等的对象必须具有相同的哈希值。

    哈希性使对象可用作字典键和集合成员,因为这些数据结构在内部使用哈希值。

    【讨论】:

      猜你喜欢
      • 2014-11-13
      • 1970-01-01
      • 1970-01-01
      • 2016-12-07
      • 2012-07-07
      • 1970-01-01
      • 1970-01-01
      • 2015-09-03
      • 2012-12-02
      相关资源
      最近更新 更多