Reputation: 16184
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
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
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
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