Recipe 1.16. Interpolating Variables in a String
Credit: Scott David
Daniels
Problem
You need a simple way to get a copy of a string where specially
marked substrings are replaced with the results of looking up the
substrings in a dictionary.
Solution
Here is a solution that works in Python 2.3 as well as in 2.4:
def expand(format, d, marker='"', safe=False):When the parameter safe is False,
if safe:
def lookup(w): return d.get(w, w.join(marker*2))
else:
def lookup(w): return d[w]
parts = format.split(marker)
parts[1::2] = map(lookup, parts[1::2])
return ''.join(parts)
if _ _name_ _ == '_ _main_ _':
print expand('just "a" test', {'a': 'one'})
# emits: just one test
the default, every marked substring must be found in dictionary
d, otherwise expand terminates with
a KeyError exception. When parameter
safe is explicitly passed as
true, marked substrings that are not found in the
dictionary are just left intact in the output string.
Discussion
The code in the
body of the expand function has some points of
interest. It defines one of two different nested functions (with the
name of lookup either way), depending on whether the
expansion is required to be safe. Safe means no
KeyError exception gets raised for marked strings
not found in the dictionary. If not required to be safe (the
default), lookup just indexes into dictionary
d and raises an error if the substring is not found.
But, if lookup is required to be
"safe", it uses
d's method get
and supplies as the default the substring being looked up, with a
marker on either side. In this way, by passing safe
as true, you may choose to have unknown formatting
markers come right through to the output rather than raising
exceptions. marker+w+marker would be an obvious
alternative to the chosen w.join(marker*2), but
I've chosen the latter exactly to display a
non-obvious but interesting way to construct such a quoted string.With either version of lookup,
expand operates according to the split/modify/join
idiom that is so important for Python string processing. The
modify part, in
expand's case, makes use of the
possibility of accessing and modifying a list's
slice with a "step" or
"stride". Specifically,
expand accesses and rebinds all of those items of
parts that lie at an odd index, because
those items are exactly the ones that were enclosed between a pair of
markers in the original format string. Therefore, they are the marked
substrings that may be looked up in the dictionary.The syntax of format strings accepted by this
recipe's function expand is more
flexible than the $-based syntax of
string.Template. You can specify a different
marker when you want your format
string to contain double quotes, for example. There is no constraint
for each specially marked substring to be an identifier, so you can
easily interpolate Python expressions (with a
d whose _ _getitem_ _
performs an eval) or any other kind of
placeholder. Moreover, you can easily get slightly different, useful
effects. For example:
print expand('just "a" "little" test', {'a' : 'one', '' : '"'})emits just one "little" test. Advanced users can
customize Python 2.4's
string.Template class, by inheritance, to match
all of these capabilities, and more, but this
recipe's little expand function is
still simpler to use in some flexible ways.
See Also
Library Reference docs for
string.Template (Python 2.4, only), the section on
sequence types (for string methods split and
join, and for slicing operations), and the section
on dictionaries (for indexing and the get method).
For more information on Python 2.4's
string.Template class, see Recipe 1.17.