问题
I am trying to use an object as the key value to a dictionary in Python. I follow the recommendations from some other posts that we need to implement 2 functions: hash and eq
And with that, I am expecting the following to work but it didn't.
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"
But it is giving me a key error message:
KeyError: 1453079729188098211
Can someone help enlightening me why this doesn't work?
回答1:
Elements of a mapping are not accessed by their hash, even though their hash is used to place them within the mapping. You must use the same value when indexing both for storage and for retrieval.
回答2:
You don't need to redefine hash and eq to use an object as dictionary key.
class Test:
def __init__(self, name):
self.name = name
test_Dict = {}
obj = Test('abc')
test_Dict[obj] = obj
print test_Dict[obj].name
This works fine and print abc.
As explained by Ignacio Vazquez-Abrams you don't use the hash of the object but the object itself as key to access the dictionary value.
The examples you found like python: my classes as dict keys. how? or Object of custom type as dictionary key redefine hash and eq for specific purpose.
For example consider these two objects obj = Test('abc') and obj2 = Test('abc').
test_Dict[obj] = obj
print test_Dict[obj2].name
This will throw a KeyError exception because obj and obj2 are not the same object.
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
This print abc. obj and obj2 are still different objects but now they have the same hash and are evaluated as equals when compared.
回答3:
Error Explanation
Given the code provided in the post, I don't actually see how you get the KeyError because you should be receiving an AttributeError (assuming the str(other,name) was a typo meant to be str(other.name)). The AttributeError comes from the __eq__ method when comparing the name of self against the name of other because your key during lookup, hash(str('abc')), is an int/long, not a Test object.
When looking up a key in a dict, the first operation performed is getting the hash of the key using the key's __hash__ method. Second, if a value for this hash exists in the dict, the __eq__ method of the key is called to compare the key against whatever value was found. This is to make sure that in the event objects with the same hash are stored in the dict (through open addressing), the correct object is retrieved. On average this lookup is still O(1).
Looking at this one step at a time, the hash of hash(str('abc')) and obj are the same. In Test, you define __hash__ as the hash of a string. When performing lookup with test_Dict[hash(str('abc'))], you are actually looking up the hash of a hash, but this is still fine since the hash of an int is itself in python.
When comparing these two values according to your defined __eq__ method, you compare the names of the objects, but the value you are comparing against is an int (hash(str('abc'))), which does not have a name property, so the AttributeError is raised.
Solution
Firstly, you do not need to (and should not) call hash() when performing the actual dict lookup since this key is also passed as the second argument to your __eq__ method. So
test_Dict[hash(str('abc'))].name
should become
test_Dict[str('abc')].name
or just
test_Dict['abc'].name
since calling str() on a string literal doesn't make much sense.
Secondly, you will need to edit your __eq__ method such that it takes into account the type of the other object you are comparing against. You have different options for this depending on what else will be stored in the same dict with your Test instances as keys.
If you plan to store your
Testinstances in a dictionary only with otherTests (or any objects that have anameproperty), then you can keep what you currently havedef __eq__(self, other): return str(self.name) == str(other.name)since you guarantee that every other key you are comparing against in the dict is of type
Testand has aname.If you plan to mix your
Testinstances in a dictionary with just strings, you will have to check if the object you compare against is a string or not since strings do not have anameproperty in python.def __eq__(self, other): if isinstance(other, str): return str(self.name) == other return str(self.name) == str(other.name)If you plan to use as keys a mix of
Tests and any other types of objects, you will need to check if theotherobject has anameto compare against.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.
I'm not much of a fan of the last 2 since you kind of go against duck typing in python with those, but there are always exceptions in life.
来源:https://stackoverflow.com/questions/17443033/using-object-as-key-in-dictionary-in-python-hash-function