import ast
import operator as python_operator


_OPERATORS = {
    ast.Add: python_operator.add,
    ast.Div: python_operator.truediv,
    ast.Mult: python_operator.mul,
    ast.Pow: python_operator.pow,
    ast.Sub: python_operator.sub,
    ast.USub: python_operator.neg,
}


def _evaluate(node, names):
    if isinstance(node, ast.Num):
        return node.n
    if isinstance(node, ast.Name):
        return names[node.id]
    if isinstance(node, ast.UnaryOp):
        operand = _evaluate(node.operand, names=names)
        return _OPERATORS[type(node.op)](operand)
    if isinstance(node, ast.BinOp):
        operand_left = _evaluate(node.left, names=names)
        operand_right = _evaluate(node.right, names=names)
        return _OPERATORS[type(node.op)](operand_left, operand_right)
    raise ValueError(node)


def evaluate_expression(expr_str, names):
    expr = ast.parse(expr_str, mode="eval")
    return _evaluate(expr.body, names=names)