Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you do a python 'eval' only within an object context?

Is it possible to do something like

c = MyObj()
c.eval("func1(42)+func2(24)")

in Python..i.e. have func1() and func2() be evaluated within the scope of the object 'c' (if they were member functions within that class definition)? I can't do a simple parsing, since for my application the eval strings can become arbitrarily complicated. I guess doing some magic with the ast module might do the trick, but due to the dirth of literature on ast, I'm not sure where to look:

import ast

class MyTransformer(ast.NodeTransformer):
    def visit_Name(self, node):
        # do a generic_visit so that child nodes are processed
        ast.NodeVisitor.generic_visit(self, node)
        return ast.copy_location(
            # do something magical with names that are functions, so that they become 
            # method calls to a Formula object
            newnode,
            node
        )

class Formula(object):

    def LEFT(self, s, n):
        return s[:n]

    def RIGHT(self, s, n):
        return s[0-n:]

    def CONCAT(self, *args, **kwargs):
        return ''.join([arg for arg in args])

def main():

    evalString = "CONCAT(LEFT('Hello', 2), RIGHT('World', 3))"

    # we want to emulate something like Formula().eval(evalString)
    node = ast.parse(evalString, mode='eval')
    MyTransformer().visit(node)

    ast.fix_missing_locations(node)
    print eval(compile(node, '<string>', mode='eval'))    
like image 864
Vineet Bansal Avatar asked Dec 17 '12 22:12

Vineet Bansal


People also ask

Why you should never use eval in Python?

eval() is considered insecure because it allows you (or your users) to dynamically execute arbitrary Python code. This is considered bad programming practice because the code that you're reading (or writing) is not the code that you'll execute.

What can I use instead of eval in Python?

literal_eval may be a safer alternative. literal_eval() would only evaluate literals, not algebraic expressions.


1 Answers

So, I advice you to do something like this:

>>> class S(object):
...     def add(self, a, b):
...         return a + b
... 
>>> filter(lambda (k,v): not k.startswith("__"), S.__dict__.items())
[('add', <function add at 0x109cec500>)]
>>> target = S()
>>> map(lambda (k, f): (k, f.__get__(target, S)), filter(lambda (k,v): not k.startswith("__"), S.__dict__.items()))
[('add', <bound method S.add of <__main__.S object at 0x109ce4ed0>>)]
>>> dict(_)
{'add': <bound method S.add of <__main__.S object at 0x109ce4ed0>>}
>>> eval("add(45, 10) + add(10, 1)", _, {})
66

Seems like that you need. Let me explain how this works.

  1. eval accepts locals and globals as parameters.
  2. So we need to define special global context which will be "representation" of your class.
  3. To do this, we need to provide as globals dictionary of all "valuable" bounded methods.
  4. Starting from simples part. We have S class definition. How to get all "valuable" methods? Simple filter names from S.__dict__ in order to check whether method name starts from __ or not (you see, that as result we get list with 1 item - add function).
  5. Create target = instance of S class which will be "eval context".
  6. Next step is most "tricky". We need to create "bound method" from each function. To do this, we use those fact, that class __dict__ stores functions, each function is non-data descriptor and bounded method can be fetched simply with func.__get__(obj, type(obj)). This operation is performed in map.
  7. Take result from previous step, create dict from it.
  8. Pass as globals to eval function.

I hope, this will help.

like image 181
Alexey Kachayev Avatar answered Oct 15 '22 11:10

Alexey Kachayev