问题
I have a class that is compared only based on the content of its attributes, so that two objects with the same values are equivalent.
class a(object):
def __init__(self, value):
self.value = value
def __repr__(self):
return 'a(value={})'.format(self.value)
def __hash__(self):
return hash(self.__repr__())
def __eq__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
return self.value == other.value
def __ne__(self, other):
return not self.__eq__(other)
When I create an object of the class and add it to a set it is added correctly. Then I create a second object with a different value and modify the first one to match the second object. The set now shows the modified object and the comparison between a1
and a2
correctly says true.
If I now try to search in the set for a1
or a2
, the set does not manage to find them, even if a1
was explicitly added and a2
compares true.
# create the object and a set with it
a1 = a(1)
a_set = set([a1])
print('Elements in set are: {}'.format(','.join([element.__repr__() for element in a_set])))
# build a new object and modify the original object to be identical to the second
a2 = a(2)
a1.value = 2
print('Elements in set are: {}'.format(','.join([element.__repr__() for element in a_set])))
# object compare fine
print('{} == {} is {}'.format(a1, a2, a1 == a2))
# none of the object are in the set (even if a1 was added to the set)
print('{} in set is {}'.format(a1, a1 in a_set))
print('{} in set is {}'.format(a2, a2 in a_set))
print('Elements in set are: {}'.format(','.join([element.__repr__() for element in a_set])))
I understand that this could be due to a1
being stored in the set under the hash corresponding to having the original value, not the modified value, but the problem in any case is that adding a2
to the set would give me a second element which is equivalent to the first. Is there a way out of this?
回答1:
The hash value of the object is stored the first time the object is stored in the set's hash table. It does not get updated even when the object is mutated.
The initial hash value hash("a(value=1)")
is what is in use not hash("a(value=2)")
.
This is one of the reasons why mutable items are never taken by sets in the first place; except you used a backdoor.
Modifying a1.value = 1
restores your object as the same match as what the set contains.
回答2:
The default python set type does not support mutable objects. You cgould design an interface that kept a set of weak references to containers containing its instances. The instances could then signal the containers when their hash changes. You'd need:
A method on instances that containers call when they add the instance
a method on containers that instances call when their hash changes
Detection of hash changes, possibly overriding setattr on the class.
来源:https://stackoverflow.com/questions/44001750/set-of-mutable-objects-gets-befuddled