$$ \newcommand{\tp}{\thinspace .} $$

 

 

 

This chapter is taken from the book A Primer on Scientific Programming with Python by H. P. Langtangen, 5th edition, Springer, 2016.

Turning user text into live objects

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

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.

Applying eval to strings

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.

Applying eval to user input

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.

The magic exec function

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

Turning string expressions into functions

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