Attributes with special behaviour in Python What is the common practice to implement attributes that have side effects in Python? I wrote the following class which supposed to save singletons when a variable is going to get a certain value. The background is that my program had sparse data, meaning that there were a lot of objects having empty sets in their attributes which wasted a lot of memory. (my actual class is slightly more complex). Here is the solution I took:

class DefaultAttr:
    void="Void"

    def __init__(self, var_name, destroy, create):
        self.var_name=var_name
        self.destroy=destroy
        self.create=create

    def __get__(self, obj, owner=None):
        result=getattr(obj, self.var_name)
        if result is DefaultAttr.void:
            result=self.create()
        return result

    def __set__(self, obj, value):
        if value==self.destroy:
            value=DefaultAttr.void
        setattr(obj, self.var_name, value)

class Test:
    a=DefaultAttr("_a", set(), set)

    def __init__(self, a):
        self._a=None
        self.a=a

t=Test(set())
print(t._a, t.a)


Do you see a way to make it neater? I was wondering if I can avoid the hidden attribute _a. Also the "Void" solution seems dodgy. What would you do?
If you are using python 2, your code doesn't work at all. I'll assume you are using python 3.

There a few cases where this will do unexpected things:

t=Test(set())
t.a.add(2)
print t.a


The set will empty.

b = set()
t = Test(b)
b.add(2)
print t.a


The set will again be empty.

These issues are caused because set are mutable. They go away if you only use immutable objects, such as frozenset(), or if you just never use the mutable operations on the sets.


  I was wondering if I can avoid the hidden attribute _a


Instead of providing the name as a parameter, have DefaultAttr make one up such as '_' + str(id(self)). That'll provide a unique attribute to store data on.


  Also the "Void" solution seems dodgy


Instead of "Void" use object(). It'll avoid a string comparison and be guaranteed unique.

Alternately, delete the attribute using delattr instead of storing anything there. Absence of the attribute indicates that you should use the default value.

Alternately, if you restrict the use of the object to immutable objects, just store a reference to the immutable object there. There is no need to use a special sentinel value. Here is my code to implement that idea:

class DefaultAttr(object):
    def __init__(self, empty):
        self._empty = empty
        self._name = '_' + str(id(self))

    def __get__(self, obj, owner=None):
        return getattr(obj, self._name)

    def __set__(self, obj, value):
        if value == self._empty:
            value = self._empty
        setattr(obj, self._name, value)



  The background is that my program had sparse data, meaning that there
  were a lot of objects having empty sets in their attributes which
  wasted a lot of memory.


Actually, your probably shouldn't be doing this at all. Instead, use frozenset(). Whenever you call frozenset() it returns the same set, so the empty sets won't take up excess memory. If you rework your app to use them, you should use much less memory.