many times, templating a string is a tidious task. using the % operator, either with tuples or dicts,<br>is difficult to maintain, when the number of templated arguments is large. and string.Template,<br>although more easy to read, is less intutive and cumbersome:
<br><br>import string<br>t = string.Template("hello $name")<br>print t.substitute({"name" : "john"})<br><br>as you can see, it is is redundant, as you must repeat the dict keys in two places, and imagine
<br>maintaining such a template with 20 parameters! if you change the one argument's name in the <br>template, you must go and fix all locations that use that template. not nice at all.<br><br>i'm suggesting something like boo's string interpolation:
<a href="http://boo.codehaus.org/String+Interpolation">http://boo.codehaus.org/String+Interpolation</a><br>but i chose to call it "evaluated string".<br><br>like raw strings (r""), which are baiscally a syntactic sugar, evaluated strings will be marked by 'e',
<br>for instance, e"", which may be combined with the 'r' or 'u', that exist today.<br><br>the evaluated string will be evaluated based on the current scope (locals and globals), just like<br>normal expressions. the difference is, the results of the expressions will be str()ed into the
<br>evaluated string directly. these expressions will be embedded into the string surrounded by<br>special delimiters, and an unclosed delimited or a syntax error will be reported at just like "\x??"<br>raises "ValueError: invalid \x escape".
<br><br>i'm not sure which delimiters to use, but i think only { } is sufficient (no need for ${ } like in boo)<br><br>some examples:<br>===============<br>name = "john"<br>print e"hello {name}"<br><br>
a = 3<br>b = 7<br>print e"the function is y = {a}x + {b}"<br>for x in range(10):<br> print e"y({x}) = {a*x+b}"<br><br>import time, sys<br>print e"the time is {time.asctime()} and you are running on {
sys.platform}"<br>===============<br><br>in order to implement it, i suggest a new type, estr. doing <br>a = e"hello"<br>will be equivalent to <br>a = estr("hello", locals(), globals()), <br>just like
<br>u"hello"<br>is equivalent to<br>unicode("hello") <br>(if we ignore \u escaping for a moment)<br><br>and just like unicode literals introduce the \u escape, estr literals would introduce \{ and \} <br>
to escape delimiters.<br><br>the estr object will be evaluated with the given locals() and globals() only at __repr__ or __str__,<br>which means you can work with it like a normal string:<br><br>a = e"hello {name} "
<br>b = e"how nice to meet you at this lovely day of {time.localtime().tm_year}"<br>c = a + b<br>c is just the concatenation of the two strings, and it will will be evaluated as a whole when you<br>str()/repr() it. of course the internal representation of the object shouldnt not as a string,
<br>rather a sequence of static (non evaluated) and dynamic (need evaluation) parts, i.e.:<br>["hello", "name", "how nice to meet you at this lovely day of", "time.localtime().tm_year"],
<br>so evaluating the string will be fast (just calling eval() on the relevant parts)<br><br>also, estr objects will not support getitem/slicing/startswith, as it's not clear what the indexes are...<br>
you'd have to first evaluate it and then work with the string:<br>str(e"hello")[2:]<br><br>estr will have a counterpart type called eunicode. some rules:<br>estr + str => estr<br>estr + estr => estr<br>estr + unicode => eunicode
<br>estr + eunicode => eunicode<br>eunicode + eunicode => eunicode<br><br>there are no backwards compatibility issues, as e"" is an invalid syntax today, and as for clarity, <br>i'm sure editors like emacs and the like can be configured to highlight the strings enclosed by {}
<br>like normal expressions.<br><br>i know it may cause the perl-syndrome, where all the code of the program is pushed into strings,<br>but templating/string interpolation is really a fundamental requirement of scripting languages,
<br>and the perl syndrome can be prevented with two precautions:<br>* compile the code with "eval" flag instead of "exec". this would prevent abominations like<br>e"{import time\ndef f(a):\n\tprint 'blah'}"
<br>* do not allow the % operator to work on estr's, to avoid awful things like<br>e"how are %s {%s}" % ("you", "name")<br>one templating mechanism at a time, please :)<br><br>perhaps there are other restrictions to impose, but i couldnt think of any at the moment.
<br><br>here's a proposed implementation:<br><br>class estr(object): # can't derive from basestring!<br> def __init__(self, raw, locals, globals):<br> self.elements = self._parse(raw)<br> self.locals = locals
<br> self.globals = globals<br> <br> def _parse(self, raw):<br> i = 0<br> last_index = 0<br> nesting = 0<br> elements = []<br> while i < len(raw):<br> if raw[i] == "{":
<br> if nesting == 0:<br> elements.append((False, raw[last_index : i]))<br> last_index = i + 1<br> nesting += 1<br> elif raw[i] == "}":
<br> nesting -= 1<br> if nesting == 0:<br> elements.append((True, raw[last_index : i]))<br> last_index = i + 1<br> if nesting < 0:<br> raise ValueError("too many '}' (at index %d)" % (i,))
<br> i += 1<br> if nesting > 0:<br> raise ValueError("missing '}' before end")<br> if last_index < i:<br> elements.append((False, raw[last_index : i]))<br> return elements
<br> <br> def __add__(self, obj):<br> if type(obj) == estr:<br> elements = self.elements + obj.elements<br> else:<br> elements = self.elements + [(False, obj)]<br> # the new object inherits the current one's namespace (?)
<br> newobj = estr("", self.locals, self.globals)<br> newobj.elements = elements<br> return newobj<br> <br> def __mul__(self, count):<br> newobj = estr("", self.locals
, self.globals)<br> newobj.elements = self.elements * count<br> return newobj<br> <br> def __repr__(self):<br> return repr(self.__str__())<br> <br> def __str__(self):<br> result = []
<br> for dynamic, elem in self.elements:<br> if dynamic:<br> result.append(str(eval(elem, self.locals, self.globals)))<br> else:<br> result.append(str(elem))<br> return "".join(result)
<br><br>myname = "tinkie winkie"<br>yourname = "la la"<br>print estr("{myname}", locals(), globals())<br>print estr("hello {myname}", locals(), globals())<br>print estr("hello {yourname}, my name is {myname}", locals(), globals())
<br><br>a = 3<br>b = 7<br>print estr("the function is y = {a}x + {b}", locals(), globals())<br>for x in range(10):<br> print estr("y({x}) = {a*x+b}", locals(), globals())<br><br>a = estr("hello {myname}", locals(), globals())
<br>b = estr("my name is {myname} ", locals(), globals())<br>c = a + ", " + (b * 2)<br>print c<br>======<br><br>the difference is that when __str__ is called, it will figure out by itself the locals() and globals()
<br>using the stack frame or whatever.<br><br><br><br>-tomer