[Tutor] What are *appropriate* uses for exec() and eval() ?
Steven D'Aprano
steve at pearwood.info
Tue Feb 17 03:15:26 CET 2015
On Mon, Feb 16, 2015 at 01:52:16PM -0600, boB Stepp wrote:
> I have heard periodically about the potential evils of using exec()
> and eval(), including today, on this list. I gather that the first
> requirement for safely using these functions is that the passed
> argument MUST be from a trusted source. So what would be examples
> where the use of these functions IS appropriate?
The flippant reply would be "there aren't any".
But that is not true.
In the broader context of programming in general, not just Python, the
use of eval/exec is incredibly powerful because it allows you to write
dynamic code that uses information available at runtime to solve
problems which cannot even be expressed at compile time.
Think about it like this:
A programming language is like a toolbox filled with tools for solving
problems. You combine those tools like Lego blocks, combining them in
different ways to make new tools. One of those Lego blocks is a robot,
called eval or exec, which you can instruct to make new tools, instead
of making them yourself.
There are various downsides: the extra complexity of telling the robot
which Lego blocks to use, instead of just directly using the blocks
yourself, means that using the robot is slower, more complex, harder to
read, error messages are less useful, and if your instructions contain
data coming from strangers, they may be able to subvert your intention,
sneak instructions into your code, and take control of the robot. But it
means you can put off the decision for which Lego block to use until
runtime when more information is available.
exec is literally a Python interpreter embedded inside Python, so if you
have a particularly hard problem to solve, one of the ways you can solve
it is to write a program to *write a program to solve it*, then use exec
to run that second program.
All this discussion has been very abstract. Here are some concrete
examples of good use of eval/exec.
In the standard library, we have the timeit module which takes a code
snippet from the user, executes it as Python code, and measures how long
it takes. There's no way to take *code* from the user except as a
string, if you type it directly Python will interpret it immediately. To
delay execution, you have to put the code inside a string, and then
later interpret the string as Python code. In other words, exec.
Likewise, we have the doctest module. Inside your function docstrings,
you can write samples of interactive output:
def spam(n):
"""Return n lots of spam.
>>> spam(3)
'spam spam spam'
"""
...
The doctest module scans the docstring, extracts anything which looks
like interactive output (starting with >>> prompt), execs that text as
Python code, and checks that the output matches what you gave it. Your
sample code is *executable documentation*, so long as you remember to
run dotest over it, you can always be sure that the sample code is
correct.
In the collections module, there is a factory function called namedtuple
for creating record-like tuples with named fields. How it works is you
provide the name of the fields, they get plugged into a class definition
template, and the template executed as Python code, which creates a new
class. That class is returned for you to use:
py> from collections import namedtuple
py> Record = namedtuple("Record", "x y z")
py> point = Record(23, 42, 19)
py> print(point)
Record(x=23, y=42, z=19)
py> point.x
23
Here is the original version which eventually became part of the
collections module:
http://code.activestate.com/recipes/500261-named-tuples/
Here is a fork of that recipe. It uses an inner class for the new
namedtuple class. The only thing which needs exec is the __new__ method.
http://code.activestate.com/recipes/578918-yet-another-namedtuple/
This demonstrates a powerful truth about Python: *most of the time* you
don't need to use exec or eval because the standard language features
are powerful enough to solve the problem for you. Using a dynamically
created inner class is *almost* enough to solve this problem, only the
__new__ method defeats it. If our requirements where just a little less
demanding, we could avoid exec completely.
In some languages, if you want to define functions are runtime, the only
way to do it is to write a function template, fill in the blanks at
runtime, then exec it:
template = """
def add(x):
return x + %s
"""
namespace = {}
exec(template % 10, namespace)
addTen = namespace['add']
print(addTen(23))
With Python, going to all that trouble is unnecessary:
def factory(n):
"""Return a new function which adds n to its argument."""
def add(x):
return x + n
return add
addTen = factory(10)
print(addTen(23))
The result is easier to read, faster, and more secure.
--
Steve
More information about the Tutor
mailing list