您可以使用ast模块并编写一个NodeVisitor,以验证每个节点的类型是否属于白名单。
import ast, math
locals = {key: value for (key,value) in vars(math).items() if key[0] != '_'}
locals.update({"abs": abs, "complex": complex, "min": min, "max": max, "pow": pow, "round": round})
class Visitor(ast.NodeVisitor):
def visit(self, node):
if not isinstance(node, self.whitelist):
raise ValueError(node)
return super().visit(node)
whitelist = (ast.Module, ast.Expr, ast.Load, ast.Expression, ast.Add, ast.Sub, ast.UnaryOp, ast.Num, ast.BinOp,
ast.Mult, ast.Div, ast.Pow, ast.BitOr, ast.BitAnd, ast.BitXor, ast.USub, ast.UAdd, ast.FloorDiv, ast.Mod,
ast.LShift, ast.RShift, ast.Invert, ast.Call, ast.Name)
def evaluate(expr, locals = {}):
if any(elem in expr for elem in '\n#') : raise ValueError(expr)
try:
node = ast.parse(expr.strip(), mode='eval')
Visitor().visit(node)
return eval(compile(node, "<string>", "eval"), {'__builtins__': None}, locals)
except Exception: raise ValueError(expr)
因为它通过白名单而不是黑名单工作,所以它是安全的。它可以访问的唯一函数和变量是您明确为其提供访问权限的函数和变量。我在字典中填充了与数学相关的函数,因此您可以根据需要轻松访问这些函数,但是必须显式使用它。
如果字符串尝试调用未提供的函数或调用任何方法,则将引发异常,并且该异常将不会执行。
因为它使用Python内置的解析器和评估器,所以它也继承了Python的优先级和升级规则。
>>> evaluate("7 + 9 * (2 << 2)")
79
>>> evaluate("6 // 2 + 0.0")
3.0
上面的代码仅在Python 3上进行了测试。
如果需要,可以在此函数上添加超时装饰器。