You can de-fang eval somewhat by using appropriate globals and locals arguments. For example, this is wat I used in a kind of calculator.
# To make eval() less dangerous by removing access
# to built-in functions.
_globals = {"__builtins__": None}
# But make standard math functions available.
_lnames = (
'acos', 'asin', 'atan', 'ceil', 'cos', 'cosh', 'e', 'log',
'log10', 'pi', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'radians'
)
_locals = {k: eval('math.' + k) for k in _lnames}
value = eval(expr, _globals, _locals)
But you schould probably screen expressions beforehand as well. Reject those that contain import or eval or exec:
if any(j in expr for j in ('import', 'exec', 'eval')):
raise ValueError('import, exec and eval are not allowed')
The module linked above also contains the use of ast to convert Python calculations into LaTeX math expressions. You could also use ast to build a custom expression evaluator.
Otherwise, here is a small stack-based postfix expression evaluator that I made.
One difference is that I added the number of arguments that each operator needs to the _ops values, so that I know how many operands to take from the stack.
import operator
import math
# Global constants {{{1
_add, _sub, _mul = operator.add, operator.sub, operator.mul
_truediv, _pow, _sqrt = operator.truediv, operator.pow, math.sqrt
_sin, _cos, _tan, _radians = math.sin, math.cos, math.tan, math.radians
_asin, _acos, _atan = math.asin, math.acos, math.atan
_degrees, _log, _log10 = math.degrees, math.log, math.log10
_e, _pi = math.e, math.pi
_ops = {
'+': (2, _add),
'-': (2, _sub),
'*': (2, _mul),
'/': (2, _truediv),
'**': (2, _pow),
'sin': (1, _sin),
'cos': (1, _cos),
'tan': (1, _tan),
'asin': (1, _asin),
'acos': (1, _acos),
'atan': (1, _atan),
'sqrt': (1, _sqrt),
'rad': (1, _radians),
'deg': (1, _degrees),
'ln': (1, _log),
'log': (1, _log10)
}
_okeys = tuple(_ops.keys())
_consts = {'e': _e, 'pi': _pi}
_ckeys = tuple(_consts.keys())
def postfix(expression): # {{{1
"""
Evaluate a postfix expression.
Arguments:
expression: The expression to evaluate. Should be a string or a
sequence of strings. In a string numbers and operators
should be separated by whitespace
Returns:
The result of the expression.
"""
if isinstance(expression, str):
expression = expression.split()
stack = []
for val in expression:
if val in _okeys:
n, op = _ops[val]
if n > len(stack):
raise ValueError('not enough data on the stack')
args = stack[-n:]
stack[-n:] = [op(*args)]
elif val in _ckeys:
stack.append(_consts[val])
else:
stack.append(float(val))
return stack[-1]