by James Tauber. See blog entry to comment.
Originally posted to the Python Web SIG back in 2003. Published here May 2006 with minor changes.
Simple string substitution can be achieved with the %
operator:
name = "Guido" print "Hello %s!" % name
...which can be used with a dictionary:
dict = {"name": "Guido"} print "Hello %(name)s!" % dict
The template can be a class, where the dictionary is passed into the
constructor and __str__
is overridden to make the
substitution:
class Template: def __init__(self, dict): self.dict = dict def __str__(self): return "Hello %(name)s!" % self.dict print Template({"name": "Guido"})
__getitem__
can be overridden to perform additional processing
on values:
class Template: def __init__(self, dict): self.dict = dict def __str__(self): return "Hello %(name)s!" % self def __getitem__(self, key): return self.dict[key].upper() print Template({"name": "Guido"})
Processing can even be driven from the template itself, with
%(...)s
referencing a function to apply to the value:
class Template: def __init__(self, dict): self.dict = dict def __str__(self): return "Hello %(name)s. Hello %(name|upper)s!" % self def __getitem__(self, key): l = key.split("|") if len(l) == 1: return self.dict[key] else: return getattr(self, l[1])(self.dict[l[0]]) def upper(self, s): return s.upper() print Template({"name": "Guido"})
Values in the dictionary can even be lists whose items are processed individually:
class Template: def __init__(self, dict): self.dict = dict def __str__(self): return "<ul>\n%(list|li)s\n</ul>" % self def __getitem__(self, key): l = key.split("|") if len(l) == 1: return self.dict[key] else: return getattr(self, l[1])(self.dict[l[0]]) def li(self, l): return "\n".join(["\t<li>%s</li>" % x for x in l]) print Template({"list": ["foo", "bar", "baz"]})
The template can be moved into a class attribute:
class Template: _template = """<ul>\n%(list|li)s\n</ul>""" def __init__(self, dict): self.dict = dict def __str__(self): return self._template % self def __getitem__(self, key): l = key.split("|") if len(l) == 1: return self.dict[key] else: return getattr(self, l[1])(self.dict[l[0]]) def li(self, l): return "\n".join(["\t<li>%s</li>" % x for x in l]) print Template({"list": ["foo", "bar", "baz"]})
In some cases, you may want a value to come from a method rather than the dictionary:
class Template: _template = """<ul>\n%(lst|li)s\n</ul>""" def __init__(self, dict={}): self.dict = dict def __str__(self): return self._template % self def __getitem__(self, key): return self._process(key.split("|")) def _process(self, l): arg = l[0] if len(l) == 1: if arg in self.dict: return self.dict[arg] elif hasattr(self, arg) and callable(getattr(self, arg)): return getattr(self, arg)() else: raise KeyError(arg) else: func = l[1] return getattr(self, func)(self._process([arg])) def lst(self): return ["foo", "bar", "baz"] def li(self, l): return "\n".join(["\t<li>%s</li>" % x for x in l]) print Template()
Now let's define a base template class and try multiple instances where we delegate formatting of the items to a different template than the overall list itself:
# the base template taken from previous example class DictionaryTemplate: def __init__(self, dict={}): self.dict = dict def __str__(self): return self._template % self def __getitem__(self, key): return self._process(key.split("|")) def _process(self, l): arg = l[0] if len(l) == 1: if arg in self.dict: return self.dict[arg] elif hasattr(self, arg) and callable(getattr(self, arg)): return getattr(self, arg)() else: raise KeyError(arg) else: func = l[1] return getattr(self, func)(self._process([arg])) # template for individual items class LI_Template: _template = """\t<li>%s</li>""" def __init__(self, input_list=[]): self.input_list = input_list def __str__(self): return "\n".join([self._template % x for x in self.input_list]) # template for overall list class UL_Template(DictionaryTemplate): _template = """<ul>\n%(lst|li)s\n</ul>""" def li(self, input_list): return LI_Template(input_list) print UL_Template({"lst": ["foo", "bar", "baz"]})
Much of the LI_Template
can be refactored into a base class
that does for lists what DictionaryTemplate
does for
dictionaries:
# assume class DictionaryTemplate exactly as before from step9 import DictionaryTemplate class ListTemplate: def __init__(self, input_list=[]): self.input_list = input_list def __str__(self): return "\n".join([self._template % x for x in self.input_list]) class LI_Template(ListTemplate): _template = """\t<li>%s</li>""" class UL_Template(DictionaryTemplate): _template = """<ul>\n%(lst|li)s\n</ul>""" def li(self, input_list): return LI_Template(input_list) print UL_Template({"lst": ["foo", "bar"]})
We can make at least two more improvements to
DictionaryTemplate
. One is to allow keyword args to the
constructor. The other is to change __process
to support
references to functions that are passed in (rather than being
defined as methods):
class DictionaryTemplate: def __init__(self, dict={}, **keywords): self.dict = dict self.dict.update(keywords) def __str__(self): return self._template % self def __getitem__(self, key): return self._process(key.split("|")) def _process(self, l): arg = l[0] if len(l) == 1: if arg in self.dict: return self.dict[arg] elif hasattr(self, arg) and callable(getattr(self, arg)): return getattr(self, arg)() else: raise KeyError(arg) else: func_name = l[1] if func_name in self.dict: func = self.dict[func_name] else: func = getattr(self, func_name) return func(self._process([arg])) # assume ListTemplate as before from step10 import ListTemplate class LI_Template(ListTemplate): _template = """\t<li>%s</li>""" class UL_Template(DictionaryTemplate): _template = """<ul>\n%(lst|li)s\n</ul>""" print UL_Template(lst=["foo", "bar", "baz", "biz"], li=LI_Template)
Here is an example which starts to show a slightly more involved template.
from step11 import DictionaryTemplate, ListTemplate # a list with no wrapper elements class ArticleList_Template(DictionaryTemplate): _template = """ <div> <h1>Articles</h1> %(lst|li)s </div>""" # a template for an article class Article_Template(ListTemplate): _template = """ <div> <h2>%(heading)s</h2> <p class="date">%(date)s</p> <p>%(abstract)s</p> <p><a href="%(link)s">Link</a></p> </div>""" # the actual data articles = [ {"heading": "Article 1", "date": "2003-02-10", "abstract": "This is the first article.", "link": "http://example.com/article/1"}, {"heading": "Article 2", "date": "2003-02-13", "abstract": "This is the second article.", "link": "http://example.com/article/2"}] print ArticleList_Template(lst=articles, li=Article_Template)