The class name, properties and property calculation formula are saved in the database. How to build a python class dynamically based on these data?
Please take a look at the following example, where the className and properties are retrieved from the database:
className = 'Person'
properties = {
'name': None,
'weight': None, # unitOfMeasure: kg
'height': None, # unitOfMeasure: m
'BMI': 'weight/(height*height)' # body mass index
}
cls_dict = {}
init_code = "def __init__(self, "
init_args = []
for prop, value in properties.items():
if value is not None:
init_code += prop + ", "
init_args.append(prop)
cls_dict[prop] = value
init_code = init_code[:-2] + "):\n"
for prop in properties:
if properties[prop] is None:
init_code += " self." + prop + " = " + prop + "\n"
else:
init_code += " self." + prop + " = eval(properties['" + prop + "'])\n"
cls_dict['__init__'] = init_code
globals()[className] = type(className, (object,), cls_dict)
Bob = Person('Bob', 77, 1.7)
print(Bob.BMI)
The error message when running the above code is:
TypeError: 'str' object is not callable
Because the class names, attributes and calculation rules saved in the database are unknown, Python classes and methods need to be dynamically constructed. Of course, classes and attributes may also be other categories of data, such as:
className = 'Product'
properties = {
'name': None,
'length': None,
'height': None,
'width': None,
'volume': 'length*height*width'
}
How can I modify my code to make it work? Or is there any other better way to achieve this function?
>Solution :
If you insist, you could maybe do something like
import dataclasses
def expr_to_func(expr, ctor_property_names):
# Not bulletproof.
for prop in ctor_property_names:
expr = expr.replace(prop, f"self.{prop}")
return eval(f"lambda self: {expr}")
def make_class(class_name, properties):
ctor_property_names = [k for k, v in properties.items() if v is None]
# Create base dataclass and add properties on it
klass = dataclasses.make_dataclass(class_name, ctor_property_names)
for prop, expr in properties.items():
if not expr:
continue
setattr(klass, prop, property(expr_to_func(expr, ctor_property_names)))
return klass
def main():
className = "Person"
properties = {
"name": None,
"weight": None,
"height": None,
"BMI": "weight/(height*height)",
}
klass = make_class(className, properties)
globals()[className] = klass # i'd advise against this
Bob = Person(name="Bob", weight=77, height=1.7)
print(Bob.BMI)
if __name__ == "__main__":
main()
but this is verging on pretty deep magic already.