Reputation: 99
I want set
/dict
to treat the objects of the same type as the single object, so there won't be more than one object of the same type in the set
/dict
.
I also want these objects to be comparable in non-trivial way, for instance against their internal state: C(value=1) == C(value=2)
must return False
and C(value=1) == C(value=1)
must return True
.
The naïve approach failed:
class Base(object):
KEY = 'base'
def __init__(self, value):
self.value = value
def __hash__(self):
# hash is the same for all objects of the same type
return hash(type(self).KEY)
def __eq__(self, other):
# objects are compared against `.value` attribute
return bool(self.value == other.value)
def __repr__(self):
return '{}({})'.format(type(self).__name__, self.value)
class Derived(Base):
KEY = 'derived'
print {Base(1), Base(2), Derived(3), Derived(4)}
# actual: set([Base(1), Base(2), Derived(3), Derived(4)])
# desired: set([Base(?), Derived(?)]) -- "?" for arbitrary value
print {Base(1): 1, Base(2): 2}
# actual: {Base(1): 1, Base(2): 2}
# desired: {Base(?): ?} -- "?" for arbitrary value
Is is it possible to store objects of user-defined class in the set/dict in a such way that there will be no more than one object in set/dict with the same class, but keep these objects still non-trivial comparable?
I know that I can store objects in dict
using type as a key:
d = {
Base: Base(1),
Derived: Derived(2),
}
but this way does not solve the issue with set
/frozenset
.
Upvotes: 2
Views: 843
Reputation: 110311
Objects that compare equal should have the same hash value - that does not preclude, by design, that objects that compare differently do have the same hash as well.
However, is hashes for objects are the same, Python then resorts to __eq__
to make the distinction among objects - that is part of the idea of hash to start with, as hash collisions may happen anywhere. If equal hashes and different equality happen, the objects are considered distinct for dictionary and set effects.
To achieve your goal of only allowing one of each object type in a dictionary or set, they have all to compare equal as well.
Therefore, the short answer to you is: it is not possible to what you want.
One workaround I suggest you is not to use __eq__
to compare these objects, and rather compare then using some other method whenever you need it, (just like Java people have to do with the .equals
method for almost everything).
You could have a helper wrapper class to be used whenever you want to compare them:
class E(object):
def __init__(self, obj):
self.obj = obj
def __eq__(self, other):
return self.obj.value == getattr(other, "obj", other).value
And them, whenever you need to perform a comparison, just do: if E(Base(1)) == Base(2): ...
Upvotes: 2