Search code examples
pythonpropertiespython-descriptors

Is there an equivalent of partialmethod for properties?


I have a class with many parameters that are stored in a dictionary as below (in practice, it's a bit more complex, but this gives a good idea). I am able to 'autogenerate' get and set methods using partialmethod:

for a_name in ['x', 'y', 'z']:
  locals()["get_" + a_name] = partialmethod(_get_arg, 
                                            arg_name=a_name) 
  locals()["set_" + a_name] = partialmethod(_set_arg, 
                                            arg_name=a_name) 

Ideally, I wanted to use @property, but only if I can 'autogenerate' @property, @setter and @deleter. Would it be possible? The second snippet show the 'manually' added properties; I am looking into using an equivalent of partialmethod to avoid repetition and unmaintainable code.

class C:
    def __init__(self):
        self.kwargs = {'x' : 0, 'y' : 3, 'z' : True}

    def __get_arg(self, arg_name):
        assert arg_name in self.kwargs
        return self.kwargs[arg_name]

    def __set_arg(self, arg_name, value):
        assert arg_name in self.kwargs
        self.kwargs[arg_name] = value

    def __del_arg(self, arg_name):
        assert arg_name in self.kwargs
        del self.kwargs[arg_name]

    @property
    def x(self):
        return self.__get_arg('x')

    @x.setter
    def x(self, value):
        self.__set_arg('x', value)

    @x.deleter
    def x(self):
        self.__del_arg('x')

    @property
    def y(self):
        return self.__get_arg('y')

    @y.setter
    def y(self, value):
        self.__set_arg('y', value)

    @y.deleter
    def y(self):
        self.__del_arg('y')

    @property
    def z(self):
        return self.__get_arg('z')

    @x.setter
    def z(self, value):
        self.__set_arg('z', value)

    @x.deleter
    def z(self):
        self.__del_arg('z')


c = C()
c.x = 'foo'  # setter called
foo = c.x    # getter called
del c.x      # deleter called

Solution

  • You can write your own descriptor type, and use the __set_name__ method that will get called on it (by the class creation machinery) to figure out what name it's been saved to in the class:

    class MyProp:
        def __set_name__(self, owner, name):
            self.name = name
    
        def __get__(self, instance, owner=None):
            if instance is None:
                 return self
            return instance._get_arg(self.name)
    
        def __set__(self, instance, value):
            instance._set_arg(self.name, value)
    
        def __delete__(self, instance):
            instance._del_arg(self.name)
    

    You'd use it this way:

     class C:
         # define __init__, _get_arg, etc.
    
         x = MyProp()
         y = MyProp()
         z = MyProp()
    

    Note that because it's code from another class calling the _X_arg methods, you probably don't want to do name mangling, so I've changed the __ prefixes to just a single _.