Django后端处理用户输入公式:替代eval的安全方案
安全处理Django中用户自定义计算公式的方案
针对你在Django应用中存储并计算用户自定义公式的需求,以下是几个避免eval/f-string安全风险的可靠方案:
1. 利用Python AST模块做安全解析
Python的ast模块可以将表达式解析为抽象语法树,你可以遍历树结构,只允许指定类型的节点(比如变量、算术操作、数字),过滤掉函数调用、属性访问等危险操作,确保执行的代码完全可控。
示例代码:
import ast def safe_eval(formula, variables): # 将用户公式中的{var}格式替换为变量名 cleaned_formula = formula.replace("{", "").replace("}", "") # 解析表达式为AST tree = ast.parse(cleaned_formula, mode='eval') # 定义允许的节点类型 allowed_nodes = (ast.Expression, ast.BinOp, ast.Name, ast.Num, ast.Add, ast.Sub, ast.Mult, ast.Div, ast.Pow) # 遍历AST节点,检查是否有非法内容 for node in ast.walk(tree): if not isinstance(node, allowed_nodes): raise ValueError(f"非法表达式内容: {type(node).__name__}") # 编译并执行安全的AST compiled = compile(tree, filename='', mode='eval') return eval(compiled, {}, variables) # 使用示例 formula = "{rower_weight}/({rower_time})^3" variables = {"rower_weight": 75, "rower_time": 12} result = safe_eval(formula, variables) print(result)
2. 使用第三方安全表达式库
比如simpleeval,它专门为安全计算表达式设计,支持自定义允许的变量、操作符和函数,默认会阻止所有危险操作。
步骤:
- 安装库:
pip install simpleeval - 编写计算逻辑:
from simpleeval import SimpleEval def calculate_formula(formula, variables): cleaned_formula = formula.replace("{", "").replace("}", "") # 初始化计算器,只允许指定变量和算术操作 evaluator = SimpleEval(names=variables) # 可以额外限制允许的操作符,比如只保留加减乘除幂 evaluator.operators = { '+': evaluator.operators['+'], '-': evaluator.operators['-'], '*': evaluator.operators['*'], '/': evaluator.operators['/'], '**': evaluator.operators['**'], } return evaluator.eval(cleaned_formula) # 使用示例 formula = "{rower_weight}/({rower_time})^3" variables = {"rower_weight": 80, "rower_time": 10} result = calculate_formula(formula, variables) print(result)
3. 自定义简单表达式解析器(适合需求单一场景)
如果你的公式逻辑很简单(仅基础算术+变量),可以自己用正则或语法分析库(比如pyparsing)实现解析,完全掌控计算逻辑。
示例(用pyparsing):
- 安装库:
pip install pyparsing - 实现解析:
from pyparsing import Word, nums, alphas, Combine, oneOf, infixNotation, opAssoc def build_parser(): # 定义数字和变量 number = Combine(Word(nums) + "." + Word(nums) | Word(nums)) variable = Word(alphas + "_") # 定义操作符 operators = oneOf("+ - * / **") # 构建表达式语法 expr = infixNotation(number | variable, [ ("**", 2, opAssoc.RIGHT), ("*", 2, opAssoc.LEFT), ("/", 2, opAssoc.LEFT), ("+", 2, opAssoc.LEFT), ("-", 2, opAssoc.LEFT), ]) return expr def evaluate_parsed_expr(parsed, variables): # 递归计算解析后的表达式 if isinstance(parsed, str): if parsed in variables: return variables[parsed] return float(parsed) op = parsed[0] left = evaluate_parsed_expr(parsed[1], variables) right = evaluate_parsed_expr(parsed[2], variables) if op == '+': return left + right elif op == '-': return left - right elif op == '*': return left * right elif op == '/': return left / right if right != 0 else 0 elif op == '**': return left ** right def custom_calculate(formula, variables): cleaned_formula = formula.replace("{", "").replace("}", "") parser = build_parser() parsed = parser.parseString(cleaned_formula, parseAll=True)[0] return evaluate_parsed_expr(parsed, variables) # 使用示例 formula = "{rower_weight}/({rower_time})^3" variables = {"rower_weight": 70, "rower_time": 15} result = custom_calculate(formula, variables) print(result)
额外注意事项
- 验证用户输入的变量名是否为系统允许的字段,避免非法变量导致的错误。
- 添加异常处理,比如除法除以零、幂运算的非法输入等情况。
- 存储公式时可以统一格式(比如去掉多余空格、统一变量占位符),减少解析时的问题。
内容的提问来源于stack exchange,提问作者Jack Miller
相关产品推荐
相关产品推荐

