chtenb
chtenb

Reputation: 16184

How to properly interpret a single line of python code?

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?

Upvotes: 6

Views: 391

Answers (3)

SingleNegationElimination
SingleNegationElimination

Reputation: 156138

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
>>> 

Upvotes: 0

Carl Younger
Carl Younger

Reputation: 3070

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.

Upvotes: 0

Blckknght
Blckknght

Reputation: 104712

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.

Upvotes: 4

Related Questions