7

I have an expression described in variable forms like this

's1*3 - (s2-s1)*1'

I have given values of s1 and s2 that can change according to the need

I can use python ast module to evaluate this expression by replacing the respective s1 and s2 values (s1 = 20,s2=30)

import ast
import operator as op

operators = {ast.Add: op.add, ast.Sub: op.sub, ast.Mult: op.mul,
             ast.Div: op.truediv, ast.Pow: op.pow, ast.BitXor: op.xor,
             ast.USub: op.neg}

def eval_(node):
    if isinstance(node, ast.Num): # <number>
        return node.n
    elif isinstance(node, ast.BinOp): # <left> <operator> <right>
        return operators[type(node.op)](eval_(node.left), eval_(node.right))
    elif isinstance(node, ast.UnaryOp): # <operator> <operand> e.g., -1
        return operators[type(node.op)](eval_(node.operand))
    else:
    raise TypeError(node)

>>> str1 = '20*3 - (30-20)*1'
>>> node = ast.parse(str1, mode='eval')
>>> eval_(node.body)
50

How should I evaluate this expression without the need to replace the variables with their actual values.

Thanks

4 Answers 4

6

To evaluate expressions you can use a NodeVisitor:

from ast import NodeVisitor

class EvalVisitor(NodeVisitor):
    def __init__(self, **kwargs):
        self._namespace = kwargs

    def visit_Name(self, node):
        return self._namespace[node.id]

    def visit_Num(self, node):
        return node.n

    def visit_NameConstant(self, node):
        return node.value

    def visit_UnaryOp(self, node):
        val = self.visit(node.operand)
        return operators[type(node.op)](val)

    def visit_BinOp(self, node):
        lhs = self.visit(node.left)
        rhs = self.visit(node.right)
        return operators[type(node.op)](lhs, rhs)

    def generic_visit(self, node):
        raise ValueError("malformed node or string: " + repr(node))

You can use this evaluator then as

v = EvalVisitor(s1=20, s2=30)
print(v.visit(node.body))

This is roughly how ast.literal_eval is implemented, with the added feature of allowing you to pass values in and without the evaluation of non-numeric expressions.

Nice trick with this operators dictionary, by the way. I'll copy that one :)

Sign up to request clarification or add additional context in comments.

2 Comments

executing "v = EvalVisitor(s1=20, s2=30)" is throwing me an error "TypeError: super() takes at least 1 argument (0 given)"
Ah, sorry, if you are on Python 2 that has to be super(EvalVisitor, self).__init__(). Actually I'm not even sure whether that line is needed at all, I don't think ast.NodeVisitor has a non-trivial constructor. I'll edit the answer.
3

You can use eval function . But you must be careful about using eval because it execute any string, can be very dangerous if you accept strings to evaluate from untrusted input. for example Suppose the string being evaluated is "os.system('rm -rf /')" ? It will really start deleting all the files on your computer.

>>> eval('20*3 - (30-20)*1')
50 

As a better solution you can parse your equation with python's internal compiler :

>>> s1=20
>>> s2=30
>>> eq='s1*3 - (s2-s1)*1'
>>> compiler.parse( eq )
Module(None, Stmt([Discard(Sub((Mul((Name('s1'), Const(3))), Mul((Sub((Name('s2'), Name('s1'))), Const(1))))))]))

So if you want to evaluate the equation , As a more safer than using input you can use compile and eval !

>>> eq='s1*3 - (s2-s1)*1'
>>> a=compile(eq,'','eval')
>>> eval(a)
50

Also you can use sympy that is a Python library for symbolic mathematics. It aims to become a full-featured computer algebra system (CAS) while keeping the code as simple as possible in order to be comprehensible and easily extensible. SymPy is written entirely in Python and does not require any external libraries.

2 Comments

I wish to compile 's1*3 - (s2-s1)*1' not '20*3 - (30-20)*1'.. Thanks
@AnuragSharma You were actually on the right track with ast. compiler has been deprecated since Python 2.6 and does not exist anymore in Python 3. I'll add an answer based on ast.
1

A bit late to the party, but for those interested: It is also possible to achieve this with a slight modification of the OP's code (which, by the way, looks remarkably similar to this). Here it is:

In the eval_ function definition, add another elif as follows:

...
elif isinstance(node, ast.Name):
    return operators[node.id]
...

Then you can simply add your variables to the operators dict. Using the OP's example:

>>> s1=20
>>> s2=30
>>> operators['s1']=s1
>>> operators['s2']=s2
>>> node = ast.parse('s1*3 - (s2-s1)*1', mode='eval')
>>> eval_(node.body)
50

Props should go to this answer based on the asteval module. Also see the asteval source.

Comments

0

The name of the node of variables is ast.Node. You can detect this node and return the corresponding value.

Make 2 following changes

def eval_(node, values_dict):
elif isinstance(node, ast.Name):
    logger.debug("Name")
    return values_dict[node.id]

Call this function like

eval_(node.body, {"s1": 10, "s2": 11})

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.