2

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.

2
  • What is the desired behaviour, if a set already contains an element of the same class, but with a different value? Should it be replaced or should the new one discarded. a=set(C(value=1)); a.add(C(value=2)); Commented Mar 9, 2016 at 14:03
  • @TheEspinosa - it does not matter, any outcome is acceptable. For instance, if there are two objects: b1 = Base(1) and b2 = Base(2), and they are added to set: s = {b1, b2}, then both outcomes set([b1]) and set([b2]) will be valid and desirable. Commented Mar 9, 2016 at 14:07

1 Answer 1

2

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): ...

Sign up to request clarification or add additional context in comments.

2 Comments

That is not 100% correct or I don't understand what you mean. The integers -1 and -2 both hash to -2, but compare different. But when the hashes match dictionaries (and sets, ...) still compare the values.
Sorry - I had the reversal role on hash and equal - fixed now,.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.