如何基于数据库存储的数据动态构建Python类?代码报错修正及优化
动态构建Python类的问题与解决方案
问题描述
从数据库获取类名、属性及属性计算公式,需要动态构建Python类。现有示例代码运行时抛出TypeError: 'str' object is not callable错误,代码如下:
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)
另外还有类似的Product类示例:
className = 'Product' properties = { 'name': None, 'length': None, 'height': None, 'width': None, 'volume': 'length*height*width' }
错误原因
代码中把__init__方法以字符串形式存入cls_dict,但type()函数创建类时,要求方法是可调用的函数对象,而非字符串,因此实例化时会触发"str对象不可调用"的错误。
修改后的可行代码
使用exec执行字符串形式的__init__代码,生成实际的函数对象:
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, " # 收集需要传入的参数:值为None的属性是必填参数 required_props = [prop for prop, val in properties.items() if val is None] init_code += ", ".join(required_props) + "):\n" # 生成__init__内部代码 for prop in properties: val_expr = properties[prop] if val_expr is None: init_code += f" self.{prop} = {prop}\n" else: # 替换表达式中的变量为self的属性引用 expr_with_self = val_expr for var in required_props: expr_with_self = expr_with_self.replace(var, f"self.{var}") init_code += f" self.{prop} = eval('{expr_with_self}')\n" # 执行代码生成__init__函数 local_vars = {} exec(init_code, globals(), local_vars) cls_dict['__init__'] = local_vars['__init__'] # 创建类 globals()[className] = type(className, (object,), cls_dict) # 测试 Bob = Person('Bob', 77, 1.7) print(Bob.BMI) # 输出约26.41
更优实现方案:使用@property避免eval风险
直接使用eval存在安全隐患(若数据库中的计算字符串被注入恶意代码),推荐用@property装饰器动态生成计算属性:
def create_dynamic_class(class_name, properties): cls_dict = {} # 定义通用__init__方法:只初始化值为None的属性 def __init__(self, *args, **kwargs): required_props = [prop for prop, val in properties.items() if val is None] # 处理位置参数 for prop, arg in zip(required_props, args): setattr(self, prop, arg) # 处理关键字参数 for prop, arg in kwargs.items(): if prop in required_props: setattr(self, prop, arg) cls_dict['__init__'] = __init__ # 为计算属性添加@property for prop, expr in properties.items(): if expr is not None: def prop_getter(self, expr=expr): # 仅传入实例的必填属性作为eval的局部变量,缩小作用域 local_vars = {k: getattr(self, k) for k in properties if properties[k] is None} return eval(expr, {}, local_vars) cls_dict[prop] = property(prop_getter) # 创建并返回类 return type(class_name, (object,), cls_dict) # 测试Person类 Person = create_dynamic_class('Person', { 'name': None, 'weight': None, 'height': None, 'BMI': 'weight/(height*height)' }) Bob = Person('Bob', 77, 1.7) print(Bob.BMI) # 输出约26.41 # 测试Product类 Product = create_dynamic_class('Product', { 'name': None, 'length': None, 'height': None, 'width': None, 'volume': 'length*height*width' }) box = Product('Box', 2, 3, 4) print(box.volume) # 输出24
这个方案的优势:
- 避免硬编码字符串形式的方法代码,可读性与维护性更强
- 使用
property实现延迟计算,若依赖的属性更新,重新访问时会自动计算新值 - 限制
eval的作用域,降低恶意代码注入的安全风险
内容的提问来源于stack exchange,提问作者joylix
相关产品推荐
相关产品推荐

