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

The alternate substitution delimiter introduced in 2.4 (string.Template) was supposed to make string substitution easier. In fact it is a little cumbersome to use. This recipe employs a little stack hackery to make it as easy as it ought to be.

Python, 13 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# Auto-prints a string using the new (as of 2.4) string-template
# based substitution.
import copy, inspect
from string import Template

def printfmt(template):
    frame = inspect.stack()[1][0]
    try:
        var = copy.copy(frame.f_globals)
        var.update(frame.f_locals)
        print Template(template).safe_substitute(var)
    finally:
        del frame

This hides having to create the Template object and call substitute/safe_substitute on it. For example:

num = 10 word = "spam"

printfmt("I would like to order $num units of $word, please")

Prints

I would like to order 10 units of spam, please

3 comments

Dirk Holtwick 16 years, 2 months ago  # | flag

Great idea. Hi Tim, your idea is great. But it didn't work for me because the index of the stack was one to much and the globals where missing. Here are my modifications and a sample:

import inspect
import copy
from string import Template

def printfmt(template):
    frame = inspect.stack()[1][0]
    try:
        var = copy.copy(frame.f_globals)
        var.update(frame.f_locals)
        print Template(template).safe_substitute(var)
    finally:
        del frame

def test():
    x = 2
    p("$x/$i")

p = printfmt
i = 1
p("$i")
test()
Tim Keating (author) 16 years, 2 months ago  # | flag

You're absolutely right. I originally tested it in the interpreter and used -1 as the stack index, which worked fine -- in the interpreter (where the call stack was only 1 deep!). After submitting the recipe I realized the stack was ordered the other way, but for some reason updated the recipe to use 2 instead of 1 as the index. Likewise, I quickly ran into the problem of not importing the globals, as you did. I have updated the recipe with your suggested changes.

Raymond Hettinger 16 years, 1 month ago  # | flag

What you lose. The template constructor is separate from the method call so that the template can be pre-compiled much like a regex. It was designed for use cases where you compile once but make many substitutions.

Also, the method had nice options like defering the choice of whether to make safe substitutions or regular substitutions. It also offered a number of options with respect to positional args, keyword args, and passing in a dictionary (locals() for example).

Also, keeping the template separate from the invocation, it is possible to substitute alternate templating styles or implementations but not having to change the client code.

See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/305306 for another approach to simplified templating.