Wednesday, March 26, 2008

String Interpolation

One of the prerequisites for recipe #1 ("Ruby-style string interpolation") from the Frame Hacks section of the "Secrets of the Framework Creators" PyCon tutorial is the string.Template class. Before I took the tutorial I was vaguely aware of the string.Template class, but I don't think I've ever used it.

I'm going to consider using it though. There are probably plenty of cases where it would be more readable than using the % operator--I can recall a couple times in code I wrote very recently.

In step 1 of Frame Hacks recipe #1, Feihong and Kumar how how one can write a function that wraps string.Template and the substitute method so the mapping argument (and optional keyword arguments) don't need to be provided.

SPOILER WARNING
def interpolate(templateStr):
import sys
from string import Template
t = Template(templateStr)
frame = sys._getframe(1)
return t.substitute(**frame.f_locals)

name = 'Feihong'
place = 'Chicago'
print interpolate("My name is ${name}. I work in ${place}.")
Here's what it could look like without the sys._getframe() call:
def interpolate(templateStr, **kws):
from string import Template
return Template(templateStr).substitute(kws)

name = 'Feihong'
place = 'Chicago'
print interpolate("My name is ${name}. I work in ${place}.",
name=name, place=place)
I guess I could argue that the latter is more explicit, but perhaps the former is explicit enough. I guess I'll be the judge of that (for myself) the next time I write (or am ready to refactor) an ugly string using the % operator. I'll let you know if I decide to add the interpolate function to my utilities module.

The solution for step 2 of recipe #1 (which I showed a partial solution for when I wrote about Frame Hacks) allows for expressions in the template strings. I don't think I've run across a use case for that, but I'll write about it if I do.

I should also mention that Python 3.0 will include a new (third) method of string interpolation, the format method to be added to the built-in string class. (There's more to it than just that. You can read all about it in PEP 3101. And if--like me--you don't know just what a PEP is, I recommend reading the "PEPs" section hear the top of the description of Python's Development Process. And you'll find plenty more detail in PEP 1.)

The above example would look like this using the new format method:
name = 'Feihong'
place = 'Chicago'
print "My name is {name}. I work in {place}.".format(name=name, place=place)
There doesn't seem to be a spec for the format method yet, so it's not clear whether one could wrap it and use sys._getframe(). I don't know if that's an argument for avoiding using it now though. (I don't see any reason why the interpolate() function above wouldn't work in Python 3.0.)

TODO

I haven't installed the latest Python 3.0 alpha yet. I should make time to do so and play with the above.

3 comments:

Feihong Hsu said...

The actual motivation for the interpolate function comes from a project at work I did. In it, I wanted to provide some scripting support for users. Rather than taking the time to teach them how to use the % operator (the users were not programmers), I figured it would be easier to just give them a function that makes interpolation more explicit.

Evan Fosmark said...

In Python 3.0, you can easily wrap around the format method in the following way:

def interpolate(templateStr):
import sys
return(templateStr.format(**sys._getframe(1).f_locals))

anentropic said...

You can do exactly the same as the second example (the one where kwargs are passed) just using the builtin string formatting... instead of ${var_name} you do %(var_name)s and pass a dict:

print "My name is %(name)s. I work in %(place)s." % {'name': name, 'place': place}

it's no more verbose and arguably just as readable.

You can even avoid typing out the dict and approximate the first example like this:

name = 'Feihong'
place = 'Chicago'
print "My name is %(name)s. I work in %(place)s." % locals()