Alternative placeholder delimiters for PEP 292

A Yet Simpler Proposal, modifying that of PEP 292 I propose that the Template module not use $ to set off placeholders; instead, placeholders are delimited by braces {}. The following rules for {}-placeholders apply: 1. {{ and }} are escapes; they are replaced with a single { or } respectively. 2. {identifier} names a substitution placeholder matching a mapping key of "identifier". By default, "identifier" must spell a Python identifier as defined in Identifiers and Keywords[1]. No other characters have special meaning. If the left-brace { is unmatched, appears at the end of the string, or is followed by any non-identifier character, a ValueError will be raised at interpolation time[2]. If a single, unmatched right-brace } occurs in the string, a ValueError will be raised at interpolation time. This avoids ambiguity: did the user want a single right-brace, or did they inadvertently omit the left-brace? This will also cause a probably erroneous "{foo}}" or "{{foo}" to raise a ValueError. Rationale There are several reasons for preferring the paired delimiters {} to a single prefixed $: 1. The placeholder name stands out more clearly from its surroundings, due to the presence of a closing delimiter, and also to the fact that the braces bear less resemblance to any alphabetic characters than the dollar sign: "Hello, {name}, how are you?" vs "Hello, $name, how are you?" 2. Only two characters have special meanings in the string, as opposed to three. Additionally, dollar signs are expected to be more often used in templated strings (e.g. for currency values) than braces: "The {item} costs ${price}." vs "The $item costs $$$price." 3. The placeholder syntax is consistent, and does not change even when valid identifier characters follow the placeholder but are not part of the placeholder: "Look at the {plural}s!" vs "Look at the ${plural}s!" 4. The template substitution could be changed in future to support dotted names without breaking existing code. The example below would break if the $-template were changed to allow dotted names: "Evaluate {obj}.__doc__" vs "Evaluate $obj.__doc__" There are two drawbacks to the proposal when compared with $-templates: 1. An extra character must be typed for each placeholder in the common case: "{name}, welcome to {site}!" vs "$name, welcome to $site!" 2. Templated strings containing braces become more complicated: "dict = {{'{key}': '{value}'}}" vs "dict = {'$key': '$value'}" The first is not a real issue; the extra closing braces needed for the placeholder when compared with the number of other characters in the templated string will usually be insignificant. Furthermore, the {}-placeholders require fewer characters to be typed in the less common case when valid identifier characters follow the placeholder but are not part of it. The need for braces in a templated string is not expected to occur frequently. Because of this, the second drawback is considered of minor importance. Reference Implementation If the general nature of feedback on this proposal is positive, or expressive of interest in an implementation, then a reference implementation will be created forthwith. References and Notes [1] Identifiers and Keywords http://www.python.org/doc/current/ref/identifiers.html [2] The requirement for interpolation-time error raising is the same as in PEP 292. Although not a part of this proposal, I suggest that it would be better if the error occured when the Template instance is created. It is worth noting that the PEP 292 reference implementation (in python/nondist/sandbox/string/string/template.py) does not fully conform to the PEP as regards raising errors for invalid template strings: >>> t = Template("This should cause an error: $*") >>> t % {} u'This should cause an error: $*' >>> t = Template("This also should cause an error: $") >>> t % {} u'This also should cause an error: $'

Andrew Durdin wrote:
Surround-style delimiting, using a single (specifiable) character. def subst(template, _sep='$', **kwds): if '' not in kwds: kwds[''] = _sep # Allow doubled _sep for _sep. parts = template.split(_sep) parts[1::2] = [kwds[element] for element in parts[1::2]] return template[0:0].join(parts) For 2.4, use a generator expression, not a list comprehension: def subst(template, _sep='$', **kwds): if '' not in kwds: kwds[''] = _sep # Allow doubled _sep for _sep. parts = template.split(_sep) parts[1::2] = (kwds[element] for element in parts[1::2]) return template[0:0].join(parts) Then you can use: subst('What I $mean$ is $$5.00', mean='really mean') or subst(u'What I $mean$ is $$5.00', mean=u'really mean') or subst('What I $mean$ is $$5.00', mean='really mean', *locals()) or ... -- Scott David Daniels Scott.Daniels@Acm.Org

On Mon, 30 Aug 2004 16:11:34 +1000, Andrew Durdin <adurdin@gmail.com> wrote:
Barry, would you care to comment on my proposal, particularly my points in the rationale for it? I've just taken the 2.4a3 Template class and modified it to fit this proposal. The result is below. I've also got a modified unit test and tex file to account for the changes at http://andy.durdin.net/test_pep292_braces.py and http://andy.durdin.net/bracetmpl.text -- I'd make a complete patch, but I'm not sure what tools to use (I'm running Win2k): can someone point me in the right direction? #################################################################### import re as _re class Template(unicode): """A string class for supporting {}-substitutions.""" __slots__ = [] # Search for {{, }}, {identifier}, and any bare {'s or }'s pattern = _re.compile(r""" (?P<escapedlt>\{{2})| # Escape sequence of two { braces (?P<escapedrt>\}{2})| # Escape sequence of two } braces {(?P<braced>[_a-z][_a-z0-9]*)}| # $ and a brace delimited identifier (?P<bogus>\{|\}) # Other ill-formed { or } expressions """, _re.IGNORECASE | _re.VERBOSE) def __mod__(self, mapping): def convert(mo): if mo.group('escapedlt') is not None: return '{' if mo.group('escapedrt') is not None: return '}' if mo.group('bogus') is not None: raise ValueError('Invalid placeholder at index %d' % mo.start('bogus')) val = mapping[mo.group('braced')] return unicode(val) return self.pattern.sub(convert, self) class SafeTemplate(Template): """A string class for supporting {}-substitutions. This class is 'safe' in the sense that you will never get KeyErrors if there are placeholders missing from the interpolation dictionary. In that case, you will get the original placeholder in the value string. """ __slots__ = [] def __mod__(self, mapping): def convert(mo): if mo.group('escapedlt') is not None: return '{' if mo.group('escapedrt') is not None: return '}' if mo.group('bogus') is not None: raise ValueError('Invalid placeholder at index %d' % mo.start('bogus')) braced = mo.group('braced') try: return unicode(mapping[braced]) except KeyError: return '{' + braced + '}' return self.pattern.sub(convert, self) del _re

Andrew Durdin wrote:
Surround-style delimiting, using a single (specifiable) character. def subst(template, _sep='$', **kwds): if '' not in kwds: kwds[''] = _sep # Allow doubled _sep for _sep. parts = template.split(_sep) parts[1::2] = [kwds[element] for element in parts[1::2]] return template[0:0].join(parts) For 2.4, use a generator expression, not a list comprehension: def subst(template, _sep='$', **kwds): if '' not in kwds: kwds[''] = _sep # Allow doubled _sep for _sep. parts = template.split(_sep) parts[1::2] = (kwds[element] for element in parts[1::2]) return template[0:0].join(parts) Then you can use: subst('What I $mean$ is $$5.00', mean='really mean') or subst(u'What I $mean$ is $$5.00', mean=u'really mean') or subst('What I $mean$ is $$5.00', mean='really mean', *locals()) or ... -- Scott David Daniels Scott.Daniels@Acm.Org

On Mon, 30 Aug 2004 16:11:34 +1000, Andrew Durdin <adurdin@gmail.com> wrote:
Barry, would you care to comment on my proposal, particularly my points in the rationale for it? I've just taken the 2.4a3 Template class and modified it to fit this proposal. The result is below. I've also got a modified unit test and tex file to account for the changes at http://andy.durdin.net/test_pep292_braces.py and http://andy.durdin.net/bracetmpl.text -- I'd make a complete patch, but I'm not sure what tools to use (I'm running Win2k): can someone point me in the right direction? #################################################################### import re as _re class Template(unicode): """A string class for supporting {}-substitutions.""" __slots__ = [] # Search for {{, }}, {identifier}, and any bare {'s or }'s pattern = _re.compile(r""" (?P<escapedlt>\{{2})| # Escape sequence of two { braces (?P<escapedrt>\}{2})| # Escape sequence of two } braces {(?P<braced>[_a-z][_a-z0-9]*)}| # $ and a brace delimited identifier (?P<bogus>\{|\}) # Other ill-formed { or } expressions """, _re.IGNORECASE | _re.VERBOSE) def __mod__(self, mapping): def convert(mo): if mo.group('escapedlt') is not None: return '{' if mo.group('escapedrt') is not None: return '}' if mo.group('bogus') is not None: raise ValueError('Invalid placeholder at index %d' % mo.start('bogus')) val = mapping[mo.group('braced')] return unicode(val) return self.pattern.sub(convert, self) class SafeTemplate(Template): """A string class for supporting {}-substitutions. This class is 'safe' in the sense that you will never get KeyErrors if there are placeholders missing from the interpolation dictionary. In that case, you will get the original placeholder in the value string. """ __slots__ = [] def __mod__(self, mapping): def convert(mo): if mo.group('escapedlt') is not None: return '{' if mo.group('escapedrt') is not None: return '}' if mo.group('bogus') is not None: raise ValueError('Invalid placeholder at index %d' % mo.start('bogus')) braced = mo.group('braced') try: return unicode(mapping[braced]) except KeyError: return '{' + braced + '}' return self.pattern.sub(convert, self) del _re
participants (3)
-
Andrew Durdin
-
Barry Warsaw
-
Scott David Daniels