Welcome, guest | Sign In | My Account | Store | Cart

Regular string interpolation in Python requires the user to pass an explicit keyword dictionary. This recipe adds a little bif of magic, so that if a name is not found in the passed dictionary, it is looked up in the locals and globals dictionaries. It is also possible not to pass any explicit dictionary, then the names is searched in the locals and globals dictionaries only.

Python, 33 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import sys, UserDict
from string import Template

class Chainmap(UserDict.DictMixin):
    """Combine multiple mappings for sequential lookup. Raymond Hettinger,
    http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/305268 """

    def __init__(self, *maps):
        self._maps = maps

    def __getitem__(self, key):
        for mapping in self._maps:
            try:
                return mapping[key]
            except KeyError:
                pass
        raise KeyError(key)

def interp(s, dic = None):
    caller = sys._getframe(1)
    if dic:
        m = Chainmap(dic, caller.f_locals, caller.f_globals)
    else:
        m = Chainmap(caller.f_locals, caller.f_globals)
    return Template(s).substitute(m)

## Example:

language="Python"

def printmsg():
    opinion = "favorite"
    print interp("My $opinion language is $language.")

This is the way I always wanted string interpolation to work.

6 comments

Nicolas Lehuen 19 years, 4 months ago  # | flag

Adding keywords ? Hi, why not add keywords to the interp() function ?

def interp(s, dic = None, **kw):
    caller = sys._getframe(1)
    if dic:
        m = Chainmap(kw, dic, caller.f_locals, caller.f_globals)
    else:
        m = Chainmap(kw, caller.f_locals, caller.f_globals)
    return Template(s).substitute(m)

This way you can write :

print interp("My $opinion language is $language, said $name.",name="Raymond")
Andreas Kloss 19 years, 4 months ago  # | flag

Default dic to {}. Then you can rip out the if-then-else

Jonathan Wright 19 years, 4 months ago  # | flag

And the cycle completes with the addition of positional args...

def interp(s, dic={}, *args, **kw):
    caller = sys._getframe(1)
    argdic = dict([(str(i),v) for i,v in enumerate(args)])
    m = Chainmap(argdic, kw, dic, caller.f_locals, caller.f_globals)
    return Template(s).substitute(m)

print interp("My $0 language is $1.", "favorite", "Python")

Yes yes, I know this is getting a little silly, but I couldn't resist.

Michele Simionato (author) 19 years, 4 months ago  # | flag

please, no dic={}. Using a mutable object as a default argument is a common mistake in Python. Consider this example for instance:

def print_default(dic, default={}):
    default.update(dic)
    print default

print_default({1:"hello"}) # the result is what you expect

print_default({2:"world"}) # the result could be not what you expect,
# unless you understand mutable objects and default arguments pretty well

The default argument is shared trough all the function calls.

Michele Simionato
tim lawrence 19 years ago  # | flag

one-liner. cat = lambda _ : Template(_).substitute(sys._getframe(1).f_locals, **sys._getframe(1).f_globals)

this works just like the old Itpl module, I think. I've used it for a couple of days, only, but it seems quite handy.

x= 'interpolate'

cat("I can $x to my heart's content.")

tim lawrence 19 years ago  # | flag

whoops! need to swap the .f_locals and the .f_globals to keep globals from hiding locals:

>>>cat = lambda _ : Template(_).substitute(sys._getframe(1).f_globals, **sys._getframe(1).f_locals)
>>>x = 'global_x'

>>>def func():

>>>    print cat('$x')

>>>    x ='local_x'

>>>    print cat('$x')

>>>test = func()

'global_x'

'local_x'