8

我正在尝试使用对象作为 Python 中字典的键值。我遵循其他一些帖子的建议,我们需要实现 2 个功能:__hash____eq__

有了这个,我期待以下工作,但它没有。

class Test:
    def __init__(self, name):
        self.name = name
        
    def __hash__(self):
        return hash(str(self.name))
        
    def __eq__(self, other):
        return str(self.name) == str(other,name)
        
        
def TestMethod():
    test_Dict = {}
    
    obj = Test('abc')
    test_Dict[obj] = obj
    
    print "%s" %(test_Dict[hash(str('abc'))].name)       # expecting this to print "abc" 

但它给了我一个关键的错误信息:

KeyError: 1453079729188098211
4

3 回答 3

15

您不需要重新定义hasheq使用对象作为字典键。

class Test:
    def __init__(self, name):
        self.name = name

test_Dict = {}

obj = Test('abc')
test_Dict[obj] = obj

print test_Dict[obj].name

这工作正常并打印abc。正如 Ignacio Vazquez-Abrams 所解释的,您不使用对象的哈希,而是使用对象本身作为访问字典值的键。


你发现的例子像python: my classes as dict keys。如何?自定义类型的对象作为字典键重新定义hasheq用于特定目的。

例如考虑这两个对象obj = Test('abc')obj2 = Test('abc')

test_Dict[obj] = obj
print test_Dict[obj2].name

这将引发KeyError异常,因为 obj 和 obj2 不是同一个对象。

class Test:
    def __init__(self, name):
        self.name = name

    def __hash__(self):
        return hash(str(self.name))

    def __eq__(self, other):
        return str(self.name) == str(other.name)

obj = Test('abc')
obj2 = Test('abc')       

test_Dict[obj] = obj
print test_Dict[obj2].name

这个打印abcobj并且obj2仍然是不同的对象,但现在它们具有相同的散列并且在比较时被评估为相等。

于 2013-07-03T10:31:15.460 回答
5

映射的元素不能通过它们的散列访问,即使它们的散列用于将它们放置在映射中。在为存储和检索建立索引时,您必须使用相同的值。

于 2013-07-03T08:22:39.330 回答
0

错误说明

鉴于帖子中提供的代码,我实际上并没有看到您是如何获得 KeyError 的,因为您应该收到一个 AttributeError (假设这str(other,name)是一个错字str(other.name))。AttributeError 来自__eq__将 self 的名称与 other 的名称进行比较时的方法,因为您在查找期间的键hash(str('abc')), 是一个 int/long,而不是一个Test对象。

在字典中查找键时,执行的第一个操作是使用键的__hash__方法获取键的哈希。其次,如果 dict 中存在此哈希的值,__eq__则调用键的方法以将键与找到的任何值进行比较。这是为了确保在具有相同哈希的事件对象存储在字典中(通过开放寻址)时,检索到正确的对象。平均而言,此查找仍然是 O(1)。

一步一步看,hash(str('abc'))和的哈希值obj是相同的。在Test中,您定义__hash__为字符串的散列。使用 执行查找时test_Dict[hash(str('abc'))],您实际上是在查找散列的散列,但这仍然很好,因为 int 的散列本身在 python 中。

根据您定义的__eq__方法比较这两个值时,您比较的是对象的名称,但您要比较的值是一个 int ( hash(str('abc'))),它没有name属性,因此引发了 AttributeError。

解决方案

首先,在执行实际的 dict 查找时,您不需要(也不应该)调用hash(),因为该键也作为第二个参数传递给您的__eq__方法。所以

test_Dict[hash(str('abc'))].name

应该成为

test_Dict[str('abc')].name

要不就

test_Dict['abc'].name

因为调用str()字符串文字没有多大意义。

其次,您需要编辑您的__eq__方法,使其考虑到other您要比较的对象的类型。您对此有不同的选择,具体取决于您将Test实例作为键存储在同一个字典中的其他内容。

  • 如果您打算Test仅将实例与其他Tests(或任何具有name属性的对象)一起存储在字典中,那么您可以保留当前拥有的

    def __eq__(self, other):
        return str(self.name) == str(other.name)
    

    因为您保证您在 dict 中与之比较的所有其他键都是类型Test并且具有name.

  • 如果您打算将字典中的实例与字符串混合,则必须检查您比较的对象是否为字符串,因为字符串在 python 中Test没有属性。name

    def __eq__(self, other):
        if isinstance(other, str):
            return str(self.name) == other
        return str(self.name) == str(other.name)
    
  • 如果您打算将 s 和任何其他类型的对象混合用作键Test,则需要检查该other对象是否具有name要比较的对象。

    def __eq__(self, other):
        if hasattr(other, "name"):
            return str(self.name) == str(other.name)
        return self.name == other  # Or some other logic since here since I don't know how you want to handle other types of classes.
    

我不是最后两个的粉丝,因为你有点反对用那些在 python 中输入鸭子,但生活中总会有例外。

于 2016-06-18T22:29:16.113 回答