replace variable names with actual values in an expression in AST python
Asked Answered
L

4

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

Luxurious answered 16/10, 2014 at 7:6 Comment(0)
S
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.

Sturges answered 16/10, 2014 at 7:21 Comment(2)
I wish to compile 's1*3 - (s2-s1)*1' not '20*3 - (30-20)*1'.. ThanksLuxurious
@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.Melanymelaphyre
M
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 :)

Melanymelaphyre answered 16/10, 2014 at 7:57 Comment(2)
executing "v = EvalVisitor(s1=20, s2=30)" is throwing me an error "TypeError: super() takes at least 1 argument (0 given)"Luxurious
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.Melanymelaphyre
S
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.

Sturges answered 16/10, 2014 at 7:21 Comment(2)
I wish to compile 's1*3 - (s2-s1)*1' not '20*3 - (30-20)*1'.. ThanksLuxurious
@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.Melanymelaphyre
X
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.

Xymenes answered 4/4, 2018 at 18:27 Comment(0)
G
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})
Giro answered 20/10, 2021 at 10:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.