'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 |
