discord.py调用ast.literal_eval报malformed node or string错误
ast.literal_eval 仅支持解析Python原生字面量(整数、浮点数、字符串、列表、字典等固定值结构),完全不支持运算表达式解析,传入5+5这类带二元运算的内容时,解析到BinOp(二元运算)节点就会抛出你看到的报错,这是函数本身的设计限制,和Python 3.10.5版本无关。
你之前测试ast.literal_eval(str(5+5))能返回10,是因为5+5在传入str()前就被Python解释器计算成了整数10,str(10)得到的是纯数字字符串'10',属于literal_eval支持的字面量范围,和表达式计算能力没有任何关系。
接入discord.py指令后,你拿到的expression是用户输入的原始字符串'5+5',套一层str()不会改变内容,传入literal_eval自然会触发相同报错。
另外你现有指令还有个参数解析问题:没加*前缀的参数只会匹配空格分隔的第一个内容,用户输入带空格的表达式(比如$math 5 + 5)时会丢失后续内容。
注意:严禁直接使用原生
eval()处理用户输入,该函数会原样执行传入的任意Python代码,恶意用户输入破坏性指令可直接接管运行机器人的服务器,造成不可逆的损失。
方案1:自行实现AST安全校验逻辑
手动遍历解析后的语法树,仅放行数学运算需要的节点类型,拦截所有函数调用、属性访问等危险语法,校验通过后再执行计算:
import ast def safe_calc(expr: str): # 白名单:仅允许数学运算相关的AST节点 allowed_node_types = { ast.Expression, ast.Constant, ast.BinOp, ast.UnaryOp, ast.Add, ast.Sub, ast.Mult, ast.Div, ast.Mod, ast.Pow, ast.USub, ast.UAdd } syntax_tree = ast.parse(expr.strip(), mode='eval') for node in ast.walk(syntax_tree): if type(node) not in allowed_node_types: raise ValueError(f"存在非法语法:{type(node).__name__}") # 仅允许数字类型的常量,拦截字符串、列表等其他内容 if isinstance(node, ast.Constant) and not isinstance(node.value, (int, float)): raise ValueError("仅支持数字运算") # 禁用内置函数,封闭执行环境 return eval(compile(syntax_tree, '<calc>', 'eval'), {"__builtins__": {}}, {})
替换原指令代码:
# 参数前加*,接收指令后所有输入内容作为表达式 @client.command() async def math(ctx, *, expression): try: result = safe_calc(expression) await ctx.send(f"计算结果:{result}") except Exception as e: await ctx.send(f"计算失败:{str(e)}")
方案2:使用成熟的安全计算库
如果不想自行维护AST校验逻辑,可以使用专门做安全表达式计算的第三方库simpleeval,默认已经做了完整的安全拦截,支持常规数学运算:
先安装依赖:
pip install simpleeval
替换指令代码:
from simpleeval import simple_eval @client.command() async def math(ctx, *, expression): try: result = simple_eval(expression) await ctx.send(f"计算结果:{result}") except Exception as e: await ctx.send(f"计算失败:{str(e)}")
内容的提问来源于stack exchange,提问作者Ruri

