Here is a safe_eval example which will ensure that the evaluated expression do not contain unsafe tokens.
It does not try to take the literal_eval approach of interpreting the AST but rather whitelist the token types and use the real eval if expression passed test.
# license: MIT (C) tardyp
import ast
def safe_eval(expr, variables):
    """
    Safely evaluate a a string containing a Python
    expression.  The string or node provided may only consist of the following
    Python literal structures: strings, numbers, tuples, lists, dicts, booleans,
    and None. safe operators are allowed (and, or, ==, !=, not, +, -, ^, %, in, is)
    """
    _safe_names = {'None': None, 'True': True, 'False': False}
    _safe_nodes = [
        'Add', 'And', 'BinOp', 'BitAnd', 'BitOr', 'BitXor', 'BoolOp',
        'Compare', 'Dict', 'Eq', 'Expr', 'Expression', 'For',
        'Gt', 'GtE', 'Is', 'In', 'IsNot', 'LShift', 'List',
        'Load', 'Lt', 'LtE', 'Mod', 'Name', 'Not', 'NotEq', 'NotIn',
        'Num', 'Or', 'RShift', 'Set', 'Slice', 'Str', 'Sub',
        'Tuple', 'UAdd', 'USub', 'UnaryOp', 'boolop', 'cmpop',
        'expr', 'expr_context', 'operator', 'slice', 'unaryop']
    node = ast.parse(expr, mode='eval')
    for subnode in ast.walk(node):
        subnode_name = type(subnode).__name__
        if isinstance(subnode, ast.Name):
            if subnode.id not in _safe_names and subnode.id not in variables:
                raise ValueError("Unsafe expression {}. contains {}".format(expr, subnode.id))
        if subnode_name not in _safe_nodes:
            raise ValueError("Unsafe expression {}. contains {}".format(expr, subnode_name))
    return eval(expr, variables)
class SafeEvalTests(unittest.TestCase):
    def test_basic(self):
        self.assertEqual(safe_eval("1", {}), 1)
    def test_local(self):
        self.assertEqual(safe_eval("a", {'a': 2}), 2)
    def test_local_bool(self):
        self.assertEqual(safe_eval("a==2", {'a': 2}), True)
    def test_lambda(self):
        self.assertRaises(ValueError, safe_eval, "lambda : None", {'a': 2})
    def test_bad_name(self):
        self.assertRaises(ValueError, safe_eval, "a == None2", {'a': 2})
    def test_attr(self):
        self.assertRaises(ValueError, safe_eval, "a.__dict__", {'a': 2})
    def test_eval(self):
        self.assertRaises(ValueError, safe_eval, "eval('os.exit()')", {})
    def test_exec(self):
        self.assertRaises(SyntaxError, safe_eval, "exec 'import os'", {})
    def test_multiply(self):
        self.assertRaises(ValueError, safe_eval, "'s' * 3", {})
    def test_power(self):
        self.assertRaises(ValueError, safe_eval, "3 ** 3", {})
    def test_comprehensions(self):
        self.assertRaises(ValueError, safe_eval, "[i for i in [1,2]]", {'i': 1})