6

I need to execute a line of python code that is entered by the user. If it is a statement I want to execute it, but if it is an expression, I want the result to be returned and do some fancy stuff with it. The problem is that python has two different functions for that, namely exec and eval.

Currently I just try to evaluate the string that the user entered. If that raises a SyntaxError, this may indicate that the string is an statement instead, so I try to execute it.

try:
    result = eval(command, scope)
except SyntaxError:
    # Probably command is a statement, not an expression
    try:
        exec(command, scope)
    except Exception as e:
        return command + ' : ' + str(e)
except Exception as e:
    return command + ' : ' + str(e)
else:
    pass # Some fancy stuff

This feels rather hacky. Is there a neater, more pythonic way to do this?

8
  • 5
    Blindly executing user-supplied code is a dangerous game. Commented May 9, 2014 at 21:56
  • Yep, I'm aware of that. But I'm implementing an embedded interpreter. Notice that I supply the scope though. Commented May 9, 2014 at 21:57
  • 2
    Close voter: this is not opinion based. I'm asking for the pythonic way, which is a generally accepted question on this site. Commented May 9, 2014 at 21:59
  • 1
    You probably want the code module. Commented May 9, 2014 at 22:05
  • @Lattyware I've looked at it and I'm not sure how. It seems that those functions do extra stuff like printing the eval result, which is not what I want. Commented May 9, 2014 at 22:15

3 Answers 3

4

While I think your existing code is probably reasonably Pythonic (under the doctrine that it's "easier to ask forgiveness than permission"), I suspect the best alternative approach is to use the ast module to inspect the code in your string:

tree = ast.parse(some_input_string)
if len(tree.body) == 1 and isinstance(tree.body[0], ast.Expr):
    result = eval(some_input_string, scope)
else:
    exec(some_input_string, scope)
    result = None

Note that some common statements are really "expression statements". So, an input string like 'do_stuff("args")' will use the eval branch of the code above, rather than the exec branch. I don't think this will have any adverse consequences, but you never know.

It is also possible to compile the tree that has been parsed and then pass the result into the eval or exec calls later. I found it rather fiddly to get right though (you need to wrap the ast.Expr's value attribute in an ast.Expression in the top branch) and so I went with the simpler (to read and understand) alternative of just passing in the string and letting Python parse it again.

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

Comments

0

You can refactor the try-except a bit. There's no real context in your example, but assuming you want to be able to execute a=1 and then evaluate a afterwards and get 1, then you could do something like...

from code import InteractiveConsole
interpreter = InteractiveConsole()

def run(code):

    try: return eval(code.strip(), interpreter.locals)
    except: pass

    try: interpreter.runcode(code)
    except Exception as error: return error

This should work for more than one line of code too.

Without knowing a bit more about your objective it's difficult to say how to do it best, but what you have is fine in principle, it just needs tidying up. This similar answer includes a minimal version of the same try-except logic again, with a focus on mimicking the interpreter more faithfully.

Comments

0

you missed one, actually there are three functions related to executing code, and the one you missed is compile().

compile() takes three required arguments, the code to be compiled, the name of the module being compiled, which will appear in tracebacks originating from that code, and a "mode". The mode argument should be one of "exec", for compiling whole modules, "eval" for compiling simple expressions, and "single", which should be a single line of interactive input!

In all three cases, you pass the returned code object to eval, with the desired context:

>>> c = compile("if 1 < 2:\n    print(3)", "<string>", "single")
>>> eval(c)
3
>>> 

3 Comments

Note that eval is returning None in your example, not 3. The 3 is being displayed because of the print call.
indeed, even simple expressions, will "return" None when compiled with "single", but will invoke sys.displayhook.
As explained in the question, when evaluating an expression, I need the result to be returned, not printed. So even when you use compile, still the decision has to be made whether to pass 'exec' or 'eval'.

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.