I l@ve RuBoard Previous Section Next Section

3.21 Evaluating Code Inside Strings

Credit: Joonas Paalasmaa

3.21.1 Problem

You have a string that contains embedded Python expressions, and you need to copy the string while evaluating those expressions.

3.21.2 Solution

This recipe's trick is to use the % string-formatting operator's named-values variant. That variant normally takes a dictionary as the righthand operand, but in fact it can take any mapping, so we just prepare a rather special mapping for the recipe's purpose:

class Eval:
    """ mapping that does expression evaluation when asked to fetch an item """
    def _ _getitem_ _(self, key):
        return eval(key)

Now we can perform feats such as:

>>> number = 20
>>> text = "python"
>>> print "%(text.capitalize())s %(number/9.0).1f rules!" % Eval(  )
Python 2.2 rules!

3.21.3 Discussion

This recipe can be seen as a templating task, akin to Recipe 3.22 and Recipe 3.23, but it is substantially simpler, because it needs to handle only embedded expressions, not statements. However, because the solution is so much simpler and faster than the general templating ones, it's better to think of this as a totally separate task.

In Python, the % operator of strings is typically used for normal formatting. The values to be interpolated in the string are the items of the righthand side, which is either a tuple, for unnamed-value formatting, or a mapping, for named-value formatting (where format items have forms such as %(name)s). The mapping is often obtained by functions such as the built-in vars, which returns a dictionary that represents the current status of local variables.

Named-value formatting is actually much more flexible. For each name string in the format, which is enclosed in parentheses after the % character that denotes the start of a format item in the format string, Python calls the get-item method of the righthand-side mapping (e.g., the special method _ _getitem_ _, when the righthand side is an instance object). That method can perform the necessary computation. The recipe shows off this possibility by simply delegating item-fetching to the built-in function eval, which evaluates the name as an expression. This can be very useful in practice, but as presented in the solution, it's limited to accessing global variables of the module in which the Eval class is itself defined. That makes it unwieldy for most practical purposes.

This problem is easily fixed, of course, because the sys._getframe function (in Python 2.1 and later) makes it easy to learn about your caller's local and global variables. So, you can tailor the evaluation environment:

import sys
class Evalx:
    def _ _init_ _(self, locals=None, globals=None):
        if locals is None: self.locals = sys._getframe(1).f_locals
        else: self.locals = locals
        if globals is None: self.globals = sys._getframe(1).f_globals
        else: self.globals = globals
    def _ _getitem_ _(self, name):
        return eval(name, self.globals, self.locals)

See Recipe 14.9 for a way to get the same functionality in other, older versions of Python.

Any instance of the Evalx class can now be used for expression evaluation, either with explicitly specified namespaces or, by default, with the local and global namespaces of the function that instantiated it.

3.21.4 See Also

Recipe 3.22, Recipe 3.23, and Recipe 14.9.

    I l@ve RuBoard Previous Section Next Section