在最近的Python3��,ast.literal_eval()“不再解析简单字符串”,而是应该使用ast.parse()方法创建AST然后进行解释。
* 更新(2023年第1季度):我有时会收到关于在这种情况下“简单字符串”的含义的评论。在阅读了当前状态的相关信息后,我添加了此更新以尝试解决这个问题。
我写了这篇答案很久以前,当时我使用了“简单字符串”这个短语,但不幸的是我不记得当时的来源,但它可能已经过时了,但确实在某个时间点上,这种方法期望的不是一个字符串。因此,在当时,这是对Python 2的引用,而这一事实在Python 3中略有变化,但仍存在一些限制。然后在某个时候,我将代码从Py2更新为Py3语法,导致了混淆。
我希望这个答案仍然是一个完整的示例,说明如何编写一个安全的解析器,可以在作者的控制下评估任意表达式,然后通过清理每个参数来解释不受控制的数据。欢迎评论,因为我仍然在现场项目中使用类似的东西!
所以现在唯一的更新是,对于非常简单的Python表达式,ast.iteral_eval(str: statements)
如果我理解正确,现在被认为是安全的。
这个答案是我希望仍然是一个工作的最小示例,展示如何实现类似于ast.literal_eval(str: statements)
的东西,以支持更多的函数、方法和数据类型,但仍然以一种简单的方式来考虑安全性。我相信还有其他方法,但那将与本问题的主题无关。
这是一个完整的示例,展示如何在Python 3.6+中正确使用ast.parse()来安全地评估简单的算术表达式。
import ast, operator, math
import logging
logger = logging.getLogger(__file__)
def safe_eval(s):
def checkmath(x, *args):
if x not in [x for x in dir(math) if not "__" in x]:
raise SyntaxError(f"Unknown func {x}()")
fun = getattr(math, x)
return fun(*args)
binOps = {
ast.Add: operator.add,
ast.Sub: operator.sub,
ast.Mult: operator.mul,
ast.Div: operator.truediv,
ast.Mod: operator.mod,
ast.Pow: operator.pow,
ast.Call: checkmath,
ast.BinOp: ast.BinOp,
}
unOps = {
ast.USub: operator.neg,
ast.UAdd: operator.pos,
ast.UnaryOp: ast.UnaryOp,
}
ops = tuple(binOps) + tuple(unOps)
tree = ast.parse(s, mode='eval')
def _eval(node):
if isinstance(node, ast.Expression):
logger.debug("Expr")
return _eval(node.body)
elif isinstance(node, ast.Str):
logger.debug("Str")
return node.s
elif isinstance(node, ast.Num):
logger.debug("Num")
return node.value
elif isinstance(node, ast.Constant):
logger.info("Const")
return node.value
elif isinstance(node, ast.BinOp):
logger.debug("BinOp")
if isinstance(node.left, ops):
left = _eval(node.left)
else:
left = node.left.value
if isinstance(node.right, ops):
right = _eval(node.right)
else:
right = node.right.value
return binOps[type(node.op)](left, right)
elif isinstance(node, ast.UnaryOp):
logger.debug("UpOp")
if isinstance(node.operand, ops):
operand = _eval(node.operand)
else:
operand = node.operand.value
return unOps[type(node.op)](operand)
elif isinstance(node, ast.Call):
args = [_eval(x) for x in node.args]
r = checkmath(node.func.id, *args)
return r
else:
raise SyntaxError(f"Bad syntax, {type(node)}")
return _eval(tree)
if __name__ == "__main__":
logger.setLevel(logging.DEBUG)
ch = logging.StreamHandler()
logger.addHandler(ch)
assert safe_eval("1+1") == 2
assert safe_eval("1+-5") == -4
assert safe_eval("-1") == -1
assert safe_eval("-+1") == -1
assert safe_eval("(100*10)+6") == 1006
assert safe_eval("100*(10+6)") == 1600
assert safe_eval("2**4") == 2**4
assert safe_eval("sqrt(16)+1") == math.sqrt(16) + 1
assert safe_eval("1.2345 * 10") == 1.2345 * 10
print("Tests pass")
ast.literal_eval("1 & 1")
将会抛出错误,而eval("1 & 1")
不会。 - Daniel van Flymenast.literal_eval
来实现这样的功能(例如,你可以手动实现一个解析器)。 - Volatility&
)被计算时,你可以使用literal_eval
。无法在那里放置任意代码以执行是一种特性而不是错误。 - Ken Williams