您可以使用 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)
因为它通过白名单而不是黑名单工作,所以它是安全的。它可以访问的唯一函数和变量是您明确授予它访问权限的函数和变量。我用与数学相关的函数填充了一个 dict,因此您可以根据需要轻松提供对这些函数的访问,但您必须明确使用它。
如果字符串试图调用尚未提供的函数,或调用任何方法,则会引发异常,并且不会执行。
因为这使用了 Python 内置的解析器和求值器,所以它也继承了 Python 的优先级和提升规则。
>>> evaluate("7 + 9 * (2 << 2)")
79
>>> evaluate("6 // 2 + 0.0")
3.0
以上代码仅在 Python 3 上测试过。
如果需要,您可以在此函数上添加超时装饰器。