In recent Python3 ast.literal_eval() "no longer parses simple strings"*, instead you are supposed to use the ast.parse() method to create an AST then interpret it.
* UPDATE (Q1:2023): I sometimes get comments about the meaning of "simple strings" in this context. On reading up on the current status I've added this update to try and addresses that.
I wrote this answer some time ago and I used the "simple string" phrase from my reference at the time, sadly I don't recall the source but it was probably getting outdated, but it is true that at one time this method expected something other than a string. So at the time this was a reference to Python 2 and that fact changed slightly in Python 3, but it does come with limitations. Then at some point I updated the code presented from Py2 to Py3 syntax, causing the confusion.
I hope this answer is still a complete example of how to write a safe parser that can evaluate arbitrary expressions under the control of the author that can then be used to interpret uncontrolled data by sanitising each parameter. Comments appreciated as I still use something similar in live projects!
So really the only update is that for very simple Python expressions ast.iteral_eval(str: statements)
is now, if I understand correctly, regarded as safe.
And this answer is I hope still a working minimal example of how to implement something similar to ast.literal_eval(str: statements)
for a greater diversity of functions, methods and datatypes but still in a simple way that can be considered safe. I am sure there are others methods but that would be out of context as unrelated to the topic of this question.
Here is a complete example of using ast.parse() correctly in Python 3.6+ to evaluate simple arithmetic expressions safely.
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")