Search code examples
pythonsetoverridingbuilt-in

How to override "set" builtin?


I want to implement the following functionality:

  1. TestClass values accepts arbitrary number of NewClass objects
  2. Only NewClass objects which do not have all the same attribute values get added to TestClass.values

I've come up with this:

class NewClass:

    def __init__(self, value1, value2):
        self.value1 = value1
        self.value2 = value2


class TestClass:

    def __init__(self, *values):
        self.values = self._set(values)

    def _set(self, object_list):
        unique_dict = {}
        for obj in object_list:
            if list(obj.__dict__.values()) not in unique_dict.values():
                unique_dict[obj] = list(obj.__dict__.values())
        return list(unique_dict.keys())


obj1 = NewClass(1, 2)
obj2 = NewClass(1, 2)
obj3 = NewClass(5, 2)

test = TestClass(obj1, obj2, obj3)

Only obj1 and obj3 are in the test.values

I am wondering how to do it in "protocol" way, such as len or add, etc.

def __len__(self):
    return len(self.values)

And does the second approach have meaningful benefits compared to the first one?


Solution

  • Assuming your value1 and value2 are immutable (integers, strings and tuples are fine; lists and dicts are not), you can hash them -- implementing both __hash__ and __eq__ will allow the built-in set type to identify duplicates.

    class NewClass:
        def __init__(self, value1, value2):
            self.value1 = value1
            self.value2 = value2
        def __hash__(self):
            return hash((self.value1, self.value2))
        def __eq__(self, other):
            return self.value1 == other.value1 and self.value2 == other.value2
        def __repr__(self):
            return 'NewClass(%r, %r)' % (self.value1, self.value2)
    
    print(set([NewClass(1,2), NewClass(1,2), NewClass(3,4)]))
    

    ...properly returns:

    {NewClass(1, 2), NewClass(3, 4)}