Alex Sidorov
Alex Sidorov

Reputation: 99

Python: how to make comparable hashable objects of the same type to be treated by set/dict as the single object?

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

Answers (1)

jsbueno
jsbueno

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

Related Questions