'How to dynamic create function with property decorator

In python, the @property and @val.setter is very helpful. For example:

from types import FunctionType
class Test:

    def __init__(self):
        self.a = 1

    @property
    def A(self):
        print('get A')
        return self.a

    @A.setter
    def A(self, val):
        print('set A')
        self.a = val

t = Test()
print(t.A)
t.A = 3
print(t.A)

It works.

Now, I want to create setProperty and getProperty for many variable, so I want to dynamic create those functions.

My code is:

from types import FunctionType
class Test:

    def __init__(self):
        self.a = 1

        code = compile('@property\ndef A(self): print("get A")\nreturn self.a', '', 'exec')
        FunctionType(code.co_consts[0], globals(), "A")

        code = compile('@A.setter\ndef A(self, val): print("set A")\nself.a=val', '', 'exec')
        FunctionType(code.co_consts[0], globals(), "A")

t = Test()
print(t.A)
t.A = 3
print(t.A)

And it reports a bug:

Traceback (most recent call last):
  File "C:/Users/Administrator/Desktop/medpro/test.py", line 23, in <module>
    t = Test()
  File "C:/Users/Administrator/Desktop/medpro/test.py", line 7, in __init__
    code = compile('@property\ndef A(self): print("get A")\nreturn self.a', '', 'exec')
  File "", line 3
SyntaxError: 'return' outside function

Then, I remove print("get A"), and another bug is reported:

Traceback (most recent call last):
  File "C:/Users/Administrator/Desktop/medpro/test.py", line 24, in <module>
    print(t.A)
AttributeError: 'Test' object has no attribute 'A'


Solution 1:[1]

You can, but do not have to use property as decorator, consider following example from property docs

class C:
    def __init__(self):
        self._x = None

    def getx(self):
        return self._x

    def setx(self, value):
        self._x = value

    def delx(self):
        del self._x

    x = property(getx, setx, delx, "I'm the 'x' property.")

Solution 2:[2]

To dynamically add descriptors, in this case read & write, you can first create a decorator which take as arguments the attributes identifiers and add them to your target class.

class DynamicDescriptors:
    def __init__(self, id_attributes: tuple):
        self.ids = id_attributes

    def __call__(self, cls):
        for attr in self.ids:
            # getter
            setattr(cls, attr, property(lambda cls_self: getattr(cls, attr)))
            # setter
            setattr(cls, attr, getattr(cls, attr).setter(lambda cls_self, v: setattr(cls, attr, v)))
            
        return cls
   

dynamic_attrs = ('a', 'b', 'c')

@DynamicDescriptors(dynamic_attrs)
class Test:
    pass

# access from instance
t = Test()
t.a = 'a'
print(t.a)
t.b = 'b'
print(t.b)
t.c = 'c'
print(t.c)

# access from class
Test.a = 10
print(Test.a)

# property object
print(Test.b)

Output

a
b
c
10
<property object at 0x7f5ff9d12c70>

EDIT (reimplementation) without decorator + support for print (or custom implementation)

There are a plenty of different ways to achieve the goal without decorator. I like to separate the tasks, so I add a classmethod which add the descriptors and the attributes are given as class attribute.

Note by personal choice I assign to a descriptor a a private name __a. Since adding the descriptor dynamically and since the private name mangling happens at compilation time the attribute __a should be called as _ClassName__a, see docs 1, 2. For non private name attribute no need for that.

class Test:

    dynamic_attrs = ('a', 'b', 'c')

    @classmethod
    def dynamic_descriptors(cls, *id_attributes):

        def prop_factory(attr):

            def getter(attr):
                def __wrapper(self):
                    p_attr = f'_{type(self).__name__}__{attr}' # private name
                    v_attr = getattr(self, p_attr)
                    print(f'getter {attr}: {v_attr}')
                    return v_attr
                return __wrapper

            def setter(attr):
                def __wrapper(self, v):
                    p_attr = f'_{type(self).__name__}__{attr}' # private name
                    old_attr = getattr(self, p_attr) if hasattr(self, p_attr) else 'None'
                    setattr(self, p_attr, v)
                    print(f'setter {attr}: {old_attr} -> {v}')
                return __wrapper

            return property(getter(attr), setter(attr))

        for attr in id_attributes:
            setattr(cls, attr, prop_factory(attr))

    def __init__(self):
        self.dynamic_descriptors(*self.dynamic_attrs)


t = Test()

print(Test.a)
#<property object at 0x7f4962138cc0>
t.a = 'aaa'
#setter a: None -> aaa
t.a
#getter a: aaa 
t.b = 'b'*4
#setter b: None -> bbbb
t.b = 'b'*2
#setter b: bbbb -> bb
t.b
#getter b: bb 
t.c = 'c'
#setter c: None -> c

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Daweo
Solution 2