Search code examples
pythonpython-decorators

Is there a faster way to use @property for multiple properties in a class?


Is there a shorter way to add properties to a class and have validation for setting than using the @property method? The below code is a sample. I would like to be able to many properties in a class that will be validated and doing it this way seems redundant. Any advice is greatly appreciated!

def valid_max(max_val=10):
"""
Decorator to check for valid value of a number between min and max

"""
def valid_value_decorator(func):
    def func_wrapper(wraps,value):
        if value <= max_val:
            return func(wraps,value)
        else:
            raise(Exception('Value above max'))
    return func_wrapper
return valid_value_decorator

class Test(object):
    def __init__(self):
        self._x=0
        self._y=0

    @property
    def x(self):
        return self._x

    @x.setter
    @valid_max(max_val=10)
    def x(self,data):
       self._x = data

    @property
    def y(self):
        return self._y

    @y.setter
    @valid_max(max_val=10)
    def y(self,data):
        self._y = data

Solution

  • You can write a factory function to produce the properties:

    def CustomProp(name, maxval):
        name = '_' + name
    
        @property
        def pro(self):
            return getattr(self, name)
    
        @pro.setter
        def pro(self, val):
            if not isinstance(val, (int, float)):
                # In Python 2 -> '' > 10(or any number) is True, so better check the type as well.
                raise TypeError('Only integers and floats are allowed.')
            if val > maxval:
                raise ValueError("Value {!r} above maximum {!r}.".format(val, maxval))
            else:
                setattr(self, name, val)
    
        return pro
    
    class Test(object):
        x = CustomProp('x', 10)
        y = CustomProp('y', 20)
        z = CustomProp('z', 30)
    
        def __init__(self, x, y, z):
            self.x = x
            self.y = y
            self.z = z
    

    Demo:

    >>> t = Test(1, 2, 3)
    >>> t.x = 10
    >>> t.y = 11
    >>> t.x = 11
    
    Traceback (most recent call last):
      File "<pyshell#41>", line 1, in <module>
        t.x = 11
      File "/home/ashwini/py/so.py", line 14, in pro
        raise ValueError("Value {!r} above maximum {!r}.".format(val, maxval))
    ValueError: Value 11 above maximum 10.
    >>> t.x = 'Python'
    
    Traceback (most recent call last):
      File "<pyshell#42>", line 1, in <module>
        t.x = 'Python'
      File "/home/ashwini/py/so.py", line 12, in pro
        raise TypeError('Only integers and floats are allowed.')
    TypeError: Only integers and floats are allowed.
    >>> t.z = 31
    
    Traceback (most recent call last):
      File "<pyshell#43>", line 1, in <module>
        t.z = 31
      File "/home/ashwini/py/so.py", line 14, in pro
        raise ValueError("Value {!r} above maximum {!r}.".format(val, maxval))
    ValueError: Value 31 above maximum 30.
    >>> t.x, t.y, t.z
    (10, 11, 3)