Question

I would like to create a class level decorator that automatically adds a property to an object, including the appropriate getter and setter methods and a backing variable. For example:

@autoproperty("foo", can_get=True, can_set=True, allow_null=False, default_value=0)
@autoproperty("baz", can_get=True, can_set=False, allow_null=True, default_value=0)
@autoproperty("bar")
class SomeNonTrivialClass(object):
   def __init__(self):
      #lots of stuff going on here

   def discombobulate(self):
      #this is obviously a very trivial example
      local_foo = self.foo;

      if (local_foo > 10):
         raise RuntimeError("Foo can never be more than 10")
      else:
         #do whatever with foo

if __name__ == '__main__':
   test = SomeNonTrivialClass()

   test.foo = 5
   test.discombobulate()
   test.foo = 11
   test.discombobulate()

I often find myself creating lots of "semi-complex" getters/setters (they could be done with just a simple property but they need default values and null protection. I would like to just be able to specify a decorator that does the heavy lifting of creating the properties on new instances of the class.

If I am way off base in this approach, I am open to an equally viable approach.

Any help would be appreciated.

I am working with python 3.X and python 2.7 so something that works in either is preferred but not necessary.

Update: I have added a bit more variety in what I am looking for. In general I need to be able to create a lot of these simple automatic properties (ala C# auto-property, but with a bit more flexibility). I do not necessarily want to expose the backing store, but I do want to make sure that an inspection of the instantiated object (not necessarily the class) shows the properties which have been created.

Was it helpful?

Solution

The following class decorator would do that:

def autoproperty(name, can_get=True, can_set=True, allow_null=False, default_value=0):
    attribute_name = '_' + name
    def getter(self):
        return getattr(self, attribute_name, default_value)
    def setter(self, value):
        if not allow_null and value is None:
            raise ValueError('Cannot set {} to None'.format(name))
        setattr(self, attribute_name, value)

    prop = property(getter if can_get else None, setter if can_set else None)

    def decorator(cls):
        setattr(cls, name, prop)
        return cls

    return decorator

but you could just as well create a property factory:

def autoproperty(attribute_name, can_get=True, can_set=True, allow_null=False, default_value=0):
    def getter(self):
        return getattr(self, attribute_name, default_value)
    def setter(self, value):
        if not allow_null and value is None:
            raise ValueError('Cannot set {} to None'.format(name))
        setattr(self, attribute_name, value)
    return property(getter if can_get else None, setter if can_set else None)

then set that in the class with:

class SomeNonTrivialClass(object):
    # ...

    foo = autoproperty('_foo', can_get=True, can_set=True, allow_null=False, default_value=0)

The class decorator would make more sense if you needed to create multiple properties (perhaps with interdependencies) instead.

OTHER TIPS

Here's a more direct approach, which avoids decorating the class.

class SomeNonTrivialClass(object):
   def __init__(self):
      #lots of stuff going on here

   foo = autoproperty("foo", can_get=True, can_set=True, allow_null=False, default_value=0)

   def discombobulate(self):
      #this is obviously a very trivial example
      local_foo = self.foo;

class autoproperty(property):
    def __init__(self, name, can_get, can_set, allow_null, default_value):
        ...
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top