You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于数据库存储的数据动态构建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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 22:35:06