This chapter is taken from the book A Primer on Scientific Programming with Python by H. P. Langtangen, 5th edition, Springer, 2016.
It is possible to provide text with valid Python code as input to a program and then turn the text into live objects as if the text were written directly into the program beforehand. This is a very powerful tool for letting users specify function formulas, for instance, as input to a program. The program code itself has no knowledge about the kind of function the user wants to work with, yet at run time the user's desired formula enters the computations.
The eval
functions takes a string as argument and
evaluates this string as a Python expression. The result of an
expression is an object. Consider
>>> r = eval('1+2')
>>> r
3
>>> type(r)
<type 'int'>
The result of r = eval('1+2')
is the same as if we had written
r = 1+2
directly:
>>> r = 1+2
>>> r
3
>>> type(r)
<type 'int'>
In general, any valid Python expression stored as text in a string s
can be turned into live Python code by eval(s)
.
Here is an example where the string to be evaluated is '2.5'
, which
causes Python to see r = 2.5
and make a float
object:
>>> r = eval('2.5')
>>> r
2.5
>>> type(r)
<type 'float'>
Let us proceed with some more examples. We can put the initialization of
a list inside quotes and use eval
to make a list
object:
>>> r = eval('[1, 6, 7.5]')
>>> r
[1, 6, 7.5]
>>> type(r)
<type 'list'>
Again, the assignment to r
is equivalent to writing
>>> r = [1, 6, 7.5]
We can also make a tuple
object by using tuple syntax (standard
parentheses instead of brackets):
>>> r = eval('(-1, 1)')
>>> r
(-1, 1)
>>> type(r)
<type 'tuple'>
Another example reads
>>> from math import sqrt
>>> r = eval('sqrt(2)')
>>> r
1.4142135623730951
>>> type(r)
<type 'float'>
At the time we run eval('sqrt(2)')
, this is the same as if we had
written
>>> r = sqrt(2)
directly, and this is valid syntax only if the sqrt
function is defined.
Therefore, the import of sqrt
prior to running eval
is
important in this example.
If we put a string, enclosed in quotes, inside the expression string, the result is a string object:
>>>
>>> r = eval('"math programming"')
>>> r
'math programming'
>>> type(r)
<type 'str'>
Note that we must use two types of quotes: first double quotes to mark
math programming
as a string object and then another set of quotes,
here single quotes (but we could also have used triple single quotes),
to embed the text "math programming"
inside a string. It does not
matter if we have single or double quotes as inner or outer quotes,
i.e., '"..."'
is the same as "'...'"
, because '
and "
are
interchangeable as long as a pair of either type is used consistently.
Writing just
>>> r = eval('math programming')
is the same as writing
>>> r = math programming
which is an invalid expression. Python will in this case think that
math
and programming
are two (undefined) variables, and setting two
variables next to each other with a space in between is invalid Python
syntax. However,
>>> r = 'math programming'
is valid syntax, as this is how we initialize a string r
in Python.
To repeat,
if we put the valid syntax 'math programming'
inside a string,
s = "'math programming'"
eval(s)
will evaluate the text inside the double quotes as
'math programming'
, which yields a string.
So, why is the eval
function so useful? When we get input via
raw_input
or sys.argv
, it is always in the form of a string
object, which often must be converted to another type of object,
usually an int
or float
. Sometimes we want to avoid specifying
one particular type. The eval
function can then be of help: we feed
the string object from the input to the eval
function and let the it
interpret the string and convert it to the right object.
An example may clarify the point. Consider a small program where we
read in two values and add them. The values could be strings, floats,
integers, lists, and so forth, as long as we can apply a +
operator
to the values. Since we do not know if the user supplies a string,
float, integer, or something else, we just convert the input by
eval
, which means that the user's syntax will determine the type.
The program goes as follows
(add_input.py):
i1 = eval(raw_input('Give input: '))
i2 = eval(raw_input('Give input: '))
r = i1 + i2
print '%s + %s becomes %s\nwith value %s' % \
(type(i1), type(i2), type(r), r)
Observe that we write out the two supplied values,
together with the types of the values (obtained by eval
), and the sum.
Let us run the program with
an integer and a real number as input:
add_input.py
Give input: 4
Give input: 3.1
<type 'int'> + <type 'float'> becomes <type 'float'>
with value 7.1
The string '4'
, returned by the first call to raw_input
,
is interpreted as an int
by eval
, while '3.1'
gives
rise to a float
object.
Supplying two lists also works fine:
add_input.py
Give input: [-1, 3.2]
Give input: [9,-2,0,0]
<type 'list'> + <type 'list'> becomes <type 'list'>
with value [-1, 3.2000000000000002, 9, -2, 0, 0]
If we want to use the program to add two strings, the strings must be
enclosed in quotes for eval
to recognize the texts as string
objects (without the quotes, eval
aborts with an error):
add_input.py
Give input: 'one string'
Give input: " and another string"
<type 'str'> + <type 'str'> becomes <type 'str'>
with value one string and another string
Not all objects are meaningful to add:
add_input.py
Give input: 3.2
Give input: [-1,10]
Traceback (most recent call last):
File "add_input.py", line 3, in <module>
r = i1 + i2
TypeError: unsupported operand type(s) for +: 'float' and 'list'
A similar program adding two arbitrary command-line arguments reads ((add_input.py):
import sys
i1 = eval(sys.argv[1])
i2 = eval(sys.argv[2])
r = i1 + i2
print '%s + %s becomes %s\nwith value %s' % \
(type(i1), type(i2), type(r), r)
Another important example on the usefulness of eval
is to turn
formulas, given as input, into mathematics in the program. Consider
the program
from math import * # make all math functions available
import sys
formula = sys.argv[1]
x = eval(sys.argv[2])
result = eval(formula)
print '%s for x=%g yields %g' % (formula, x, result)
Two command-line arguments are expected: a formula and a number. Say
the formula given is 2*sin(x)+1
and the number is 3.14. This
information is read from the command line as strings. Doing x =
eval(sys.argv[2])
means x = eval('3.14')
, which is equivalent to x
= 3.14
, and x
refers to a float
object. The eval(formula)
expression means eval('2*sin(x)+1')
, and the corresponding statement
result = eval(formula
is therefore effectively result =
2*sin(x)+1
, which requires sin
and x
to be defined objects. The
result is a float
(approximately 1.003). Providing cos(x)
as the
first command-line argument creates a need to have cos
defined, so
that is why we import all functions from the math
module.
Let us try to run the program:
eval_formula.py "2*sin(x)+1" 3.14
2*sin(x)+1 for x=3.14 yields 1.00319
The very nice thing with using eval
in x = eval(sys.argv[2])
is that
we can provide mathematical expressions like pi/2
or even
tanh(2*pi)
, as the latter just effectively results in the
statement x = tanh(2*pi)
, and this works fine as long has we have
imported tanh
and pi
.
Having presented eval
for turning strings into Python code, we take
the opportunity to also describe the related exec
function to
execute a string containing arbitrary Python code, not only an
expression.
Suppose the user can write a formula as input to the
program, available to us in the form of a string object.
We would then like to turn this formula into a callable Python
function. For example, writing sin(x)*cos(3*x) + x**2
as the formula,
we would make the function
def f(x):
return sin(x)*cos(3*x) + x**2
This is easy with exec
: just construct the right Python syntax
for defining f(x)
in a string and apply exec
to the string,
formula = sys.argv[1]
code = """
def f(x):
return %s
""" % formula
from math import * # make sure we have sin, cos, exp, etc.
exec(code)
As an example,
think of "sin(x)*cos(3*x) + x**2"
as the first command-line
argument. Then formula
will hold this text, which is inserted into
the code
string such that it becomes
"""
def f(x):
return sin(x)*cos(3*x) + x**2
"""
Thereafter, exec(code)
executes the code as if we had written
the contents of the code
string directly into the program by hand.
With this technique, we can turn any user-given formula into a
Python function!
Let us now use this technique in a useful application. Suppose we have made a function for computing the integral \( \int_a^b f(x)dx \) by the Midpoint rule with \( n \) intervals:
def midpoint_integration(f, a, b, n=100):
h = (b - a)/float(n)
I = 0
for i in range(n):
I += f(a + i*h + 0.5*h)
return h*I
We now want to read \( a \), \( b \), and \( n \) from the command line as well
as the formula that makes up the \( f(x) \) function:
from math import *
import sys
f_formula = sys.argv[1]
a = eval(sys.argv[2])
b = eval(sys.argv[3])
if len(sys.argv) >= 5:
n = int(sys.argv[4])
else:
n = 200
Note that we import everything from math
and use eval
when
reading the input for a
and b
as this will allow the user to
provide values like 2*cos(pi/3)
.
The next step is to convert the f_formula
for \( f(x) \) into a
Python function g(x)
:
code = """
def g(x):
return %s
""" % f_formula
exec(code)
Now we have an ordinary Python function g(x)
that we can ask
the integration function to integrate:
I = midpoint_integration(g, a, b, n)
print 'Integral of %s on [%g, %g] with n=%d: %g' % \
(f_formula, a, b, n, I)
The complete code is found in integrate.py. A sample run for \( \int_0^{\pi/2} \sin(x)dx \) goes like
integrate.py "sin(x)" 0 pi/2
integral of sin(x) on [0, 1.5708] with n=200: 1
(The quotes in "sin(x)"
are needed because of the parenthesis will
otherwise be interpreted by the shell.)
The examples in the previous section indicate that it can be handy
to ask the user for a formula and turn that formula into a Python
function.
Since this operation is so useful, we have made a special tool that
hides the technicalities. The tool is named
StringFunction
and works as follows:
>>> from scitools.StringFunction import StringFunction
>>> formula = 'exp(x)*sin(x)'
>>> f = StringFunction(formula) # turn formula into f(x) func.
The f
object now behaves as an ordinary Python function of x
:
>>> f(0)
0.0
>>> f(pi)
2.8338239229952166e-15
>>> f(log(1))
0.0
Expressions involving other independent variables than x
are also
possible.
Here is an example with the function
\( g(t) = Ae^{-at}\sin (\omega x) \):
g = StringFunction('A*exp(-a*t)*sin(omega*x)',
independent_variable='t',
A=1, a=0.1, omega=pi, x=0.5)
The first argument is the function formula, as before, but now we need
to specify the name of the independent variable ('x'
is default).
The other parameters in the function (\( A \), \( a \), \( \omega \), and \( x \)) must be
specified with values, and we use keyword arguments, consistent with
the names in the function formula, for this purpose. Any of the parameters
A
, a
, omega
, and x
can be changed later by calls like
g.set_parameters(omega=0.1)
g.set_parameters(omega=0.1, A=5, x=0)
Calling g(t)
works as if g
were a plain Python
function of t
, which
also stores all the parameters A
, a
, omega
, and
x
, and their values. You can use pydoc
to bring
up more documentation on the possibilities with StringFunction
.
Just run
pydoc scitools.StringFunction.StringFunction
A final important point is that StringFunction
objects are as
computationally efficient as hand-written Python functions.
(This property is quite remarkable, as a string formula will in most
other programming languages be much slower to evaluate than if the
formula were hardcoded inside a plain function.)