2

Let's say i get a string to eval

temperature*x

And I have two sets of variables - the easy one:

easy_ns = {'x':3, 'y':4}

And a harder one:

harder = ['temperature', 'sumofall']

Each of which will take significant time to calculate and I don't want to calculate them unless they are part of the user supplied expression

E.g. I don't want to start the detection of "temperature" unless I know it is required

I may have some variables in my namespace that are "inexpensive" but others I would like to postpone calculating as much as possible

How do I get a list of variables from my eval string before it is evaluated

I know I can try: eval() except: and I will get a:

NameError: name 'temperature' is not defined

Is there a pythonic way of extracting the exact variable name?

Is there a nice way to build your namespace for lazy evaluation?

Something like

namespace = {'x':3, 'y':4, 'temperature':lazy_temperature_function}

So that only when my expression is evaluated

res=eval('temperature*x')

is my lazy temperature function called

And yes of course - I absolutely do have to use 'eval' - that is why I have posted these questions

The scenario is that I get an input file with set of keys and values and then the user can supply an expression he wants me to calculate from a combination of those values and some generated variables that I do not want to calculate unless the user includes them in his/her expression

14
  • 1
    Why are you doing this to yourself? This sounds like it is going to be hard to program and hard to use. What are you really wanting to do? Commented Jun 23, 2016 at 13:34
  • 7
    Have you considered absolutely not trying to evaluate the string? Commented Jun 23, 2016 at 13:34
  • 1
    You want to parse a code, but on execute it (at least yet). That's what ast is for. Commented Jun 23, 2016 at 13:49
  • 1
    I refuse to believe that you "absolustely have to use eval." Either you or another programmer whose output you are consuming has made a terrible decision that has left you in this spot. You should undo that decision if at all possible. Commented Jun 23, 2016 at 13:51
  • 3
    "Is there a nice way to build your namespace for lazy evaluation?" - there is no reason your namespace couldn't be a dictionary wrapper with a lazy implementation of __getitem__. Commented Jun 23, 2016 at 14:04

2 Answers 2

10

You could, if you really really have to, parse the code using the ast module. The ast.parse helper will give you an AST tree representation of the code:

import ast
code = "temperature*x"
st = ast.parse(code)
for node in ast.walk(st):
    if type(node) is ast.Name:
        print(node.id)

This will print:

temperature
x

So this only extracts the variable names, like you said. It seems like a first step, but I'm not sure what you are trying to do so maybe a different approach is better.

Edit: If I understand your problem correctly, you want some values to be calculated only if they appear in an expression? I tried something like this:

>>> import ast
>>> code = "temperature*x"
>>> x = 5
>>> def lazy_temperature():
    return 78
... 
>>> names = [node.id for node in ast.walk(ast.parse(code))
             if type(node) is ast.Name]
>>> ns = {name: (globals()['lazy_%s' % name])()
                 if ('lazy_%s' % name) in globals()
                 else globals()[name] 
          for name in names}
>>> ns
{'x': 5, 'temperature': 78}
>>> eval(code, ns)
390

This snippet will load the value out of the current scope, unless there's a function called lazy_<name>. This function will be called in case the <name> part appears in the expression.

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

3 Comments

Would the downvoter care to elaborate? Just curious.
I have posted a modified question in stackoverflow.com/questions/37995920/… and hope that will attract more constructive feedback. Please post your answer there as well. I think I may use both parts of your answer in my implementation
it seems the lazy eval code doesn't work. eval result of the code does not change if you change the lazy return value
1

You could make it a lambda function, and simply execute it whenever you need to, as such:

a = lambda : 5*temperature
a()
> Traceback (most recent call last):
>   File "<stdin>", line 1, in <module>
>   File "<stdin>", line 1, in <lambda>
> NameError: global name 'temperature' is not defined
temperature = 100
a()
> 500

In this way, you don't look for execute unless you consciously want to.


However, you could also make sure to only enter the lambda function if temperature exists. You can do that by assigning temperature to None in the beginning of your file, and only enter the lambda if you need to:

temperature = None
if temperature:
  a()
# do something else

If you don't want to use the parens `fn() - You could also build a class to do this with a property.

class a(object):
  @property
  def temp_calc(self):
    return self.temp*5

In this way, you can do the following:

temp_obj = a()
temp_obj.temp_calc

This will return an error since you don't have a "temp" attribute. But you can assign it if you need to:

temp_obj.temp = 5
temp_obj.temp_calc
> 25

There are lots of options here, i hope these few help.

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.