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

Python如何不使用eval将类实例化字符串转为实际函数调用

无eval实现类实例字符串解析的方案

哪怕你完全信任数据库写入的内容,也可以通过AST解析+标准库动态导入的方式实现需求,全程避免执行任意动态代码,安全性和可控性远高于eval/exec。

核心实现逻辑分5步:

  • 用ast.parse将字符串解析为语法树,强制校验语句格式必须为合法的可调用对象调用,从根源拦截非预期语法
  • 拆分调用链,区分模块路径与目标类/可调用对象名称
  • 用importlib.import_module替代exec实现模块动态导入,通过getattr逐层获取目标类
  • 用ast.literal_eval解析构造参数,该方法仅能解析字符串、数字、布尔、列表、字典等Python字面量,无法执行函数调用或其他恶意逻辑
  • 传入解析完成的参数完成实例化

完整实现代码如下:

import ast
import importlib

def safe_create_instance(statement: str, base_module: str = "parent.module"):
    # 解析AST,仅允许单一的调用表达式
    tree = ast.parse(statement, mode="eval")
    call_node = tree.body
    if not isinstance(call_node, ast.Call):
        raise ValueError(f"非法的实例化语句: {statement}, 仅允许类/函数调用格式")
    
    # 拆分完整调用属性链
    attr_chain = []
    current = call_node.func
    while isinstance(current, ast.Attribute):
        attr_chain.append(current.attr)
        current = current.value
    if not isinstance(current, ast.Name):
        raise ValueError(f"非法的调用目标格式: {statement}")
    attr_chain.append(current.id)
    attr_chain.reverse()
    if len(attr_chain) < 2:
        raise ValueError(f"调用目标必须包含模块路径: {statement}")
    
    # 动态导入模块,优先从指定的base_module下查找,兼容原有导入逻辑
    target = None
    remaining_attrs = []
    # 从长到短尝试匹配可导入的模块路径
    for split_idx in range(len(attr_chain), 0, -1):
        try:
            mod_candidate = ".".join([base_module] + attr_chain[:split_idx])
            target = importlib.import_module(mod_candidate)
            remaining_attrs = attr_chain[split_idx:]
            break
        except ImportError:
            continue
    # 基础路径下找不到时尝试绝对导入
    if target is None:
        for split_idx in range(len(attr_chain), 0, -1):
            try:
                mod_candidate = ".".join(attr_chain[:split_idx])
                target = importlib.import_module(mod_candidate)
                remaining_attrs = attr_chain[split_idx:]
                break
            except ImportError:
                continue
    if target is None:
        raise ImportError(f"无法导入语句对应的模块: {statement}")
    # 顺着属性链拿到最终要实例化的类/可调用对象
    for attr in remaining_attrs:
        target = getattr(target, attr)
    
    # 解析构造参数,仅支持字面量值
    args = []
    for arg_node in call_node.args:
        try:
            args.append(ast.literal_eval(arg_node))
        except ValueError:
            raise ValueError(f"构造参数仅支持字面量值,非法参数位置: {ast.dump(arg_node)}")
    kwargs = {}
    for kw_node in call_node.keywords:
        try:
            kwargs[kw_node.arg] = ast.literal_eval(kw_node.value)
        except ValueError:
            raise ValueError(f"构造参数仅支持字面量值,非法参数{kw_node.arg}: {ast.dump(kw_node)}")
    
    # 完成实例化
    return target(*args, **kwargs)


# 调用示例
statement = r'module1.CustomHandler(filename="abc.csv", mode="rb")'
instance = safe_create_instance(statement)

注意:如果需要支持非字面量参数(如枚举、预定义常量),请在参数解析环节增加严格白名单,仅放行可信的可调用对象/常量,不要放开任意代码执行权限。

如果你的业务场景格式非常固定,也可以通过正则拆分字符串实现,但AST方案的鲁棒性更强,不会因为参数值包含括号、点号等特殊字符出现解析错误。

内容的提问来源于stack exchange,提问作者Foocli

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 16:18:21