I'm assuming "don't give others solutions for the problem, because I know it's very easy to do by a diferent way" is referring to eval
as the easy answer you don't want.
But really, what you want is almost certainly a function that parses and interprets an expression in some language, just like eval
does, it's just that you want a simpler and more restricted language than Python, maybe just basic arithmetic expressions.
As Olivier Melançon's answer shows, Python also comes with an interpreter for a limited subset of Python in the f-string evaluator, and as jedwards' answer shows, there are readily-available interpreters for what are effectively similar languages to such a subset.
But if you want to handle exactly the language you want in exactly the way you want, you have to do it yourself. And it's not actually as scary as it sounds. I'll use Python's own ast
module for parsing, but then write my own interpreter. This works as long as you really want a strict subset of Python, but if you want something different—e.g., ^
for exponentiation (which has the wrong precedence and associativity), or variables that aren't valid Python identifiers, etc., you'll have to build the parser too.
import ast
import operator
ops = {
ast.Add: operator.add,
ast.Sub: operator.sub,
ast.Mult: operator.mul,
ast.Div: operator.truediv,
}
def interpret(expr, env):
if isinstance(expr, ast.Expr):
return interpret(expr.value, env)
elif isinstance(expr, ast.BinOp):
op = ops[type(expr.op)]
return op(interpret(expr.left, env), interpret(expr.right, env))
elif isinstance(expr, ast.Num):
return expr.n
elif isinstance(expr, ast.Name):
val = env[expr.id]
if isinstance(val, (bool, int, float, complex)):
return val
raise ValueError(val)
else:
raise ValueError(expr)
def evaluate(s, env=None):
if env is None: env = globals()
tree = ast.parse(s)
return interpret(tree.body[0], env)
Now, you can evaluate anything using the basic 4 arithmetic operators, parentheses, numeric constants, or the names of global variables whose values are numbers, but nothing else:
>>> a = 3
>>> b = 7
>>> evaluate('a * b')
21
>>> evaluate('3*a + 2*(b+10)')
32
>>> evaluate('__import__("os")')
ValueError: <_ast.Call object at 0x109210da0>
>>> evaluate('a * b', {'a': 10, 'b': 1j})
10j
>>> evaluate('-2')
ValueError: <_ast.UnaryOp object at 0x1092fc5c0>
Obviously the error handling could be nicer, and I probably should have handled at least unary -
in case you want negative numbers… but this should be enough to get the point across. And reading the ast
docs linked above, it should be clear how to extend this further.
On the other hand, Bijoy's answer is an even simpler parser and interpreter. It won't handle more complicated expressions—but if you don't want it to, that's a feature, not a bug.