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でのみテストされています。
必要に応じて、この関数にタイムアウトデコレータを追加できます。