proposal to allow to set the delimiter in str.format to something other than curly bracket

Alia Khouri alia_khouri at
Mon Apr 4 17:42:36 CEST 2011

Terry Reedy wrote:

> Just double the brackets, just as one doubles '\\' to get '\' in a string.
>  >>> "class {0}Model {{ public bool IsModel(){{ returntrue; }}}}".format('My')
> 'class MyModel { public bool IsModel(){ returntrue; } }'

Indeed, I tried that, but it means I have to double bracket all the
csharp code which just creates more work for me.

Here is my solution (aka Cheetah ultra-light) ripped from
stdlib.string: it involves hacking the string.Template class so that
you get attribute lookups. I know it uses eval, and it's probably
highly insecure, but it's for one off batch jobs, and I hope it
doesn't hurt anyone:

import re

class _multimap:
    """Helper class for combining multiple mappings.

    Used by .substitute() to combine the mapping and keyword
    def __init__(self, primary, secondary):
        self._primary = primary
        self._secondary = secondary

    def __getitem__(self, key):
            return self._primary[key]
        except KeyError:
            return self._secondary[key]

class _TemplateMetaclass(type):
    pattern = r"""
      (?P<escaped>%(delim)s) |   # Escape sequence of two delimiters
      (?P<named>%(id)s)      |   # delimiter and a Python identifier
      {(?P<braced>%(id)s)}   |   # delimiter and a braced identifier
      (?P<invalid>)              # Other ill-formed delimiter exprs

    def __init__(cls, name, bases, dct):
        super(_TemplateMetaclass, cls).__init__(name, bases, dct)
        if 'pattern' in dct:
            pattern = cls.pattern
            pattern = _TemplateMetaclass.pattern % {
                'delim' : re.escape(cls.delimiter),
                'id'    : cls.idpattern,
        cls.pattern = re.compile(pattern, re.IGNORECASE | re.VERBOSE)

class Template:
    """A string class for supporting $-substitutions."""
    __metaclass__ = _TemplateMetaclass

    delimiter = '$'
    idpattern = r'[_a-z][_a-z0-9\.]*'

    def __init__(self, template):
        self.template = template

    # Search for $$, $identifier, ${identifier}, and any bare $'s

    def _invalid(self, mo):
        i = mo.start('invalid')
        lines = self.template[:i].splitlines(True)
        if not lines:
            colno = 1
            lineno = 1
            colno = i - len(''.join(lines[:-1]))
            lineno = len(lines)
        raise ValueError('Invalid placeholder in string: line %d, col
%d' %
                         (lineno, colno))

    def substitute(self, *args, **kws):
        if len(args) > 1:
            raise TypeError('Too many positional arguments')
        if not args:
            mapping = kws
        elif kws:
            mapping = _multimap(kws, args[0])
            mapping = args[0]
        # Helper function for .sub()
        def convert(mo):
            # Check the most common path first.
            named ='named') or'braced')
            if named is not None:
                # We use this idiom instead of str() because the
latter will
                # fail if val is a Unicode containing non-ASCII
                # XXXX here is the probably dangerous eval hack XXXXXX
                if '.' in named:
                    return eval(named, kws)
                    val = mapping[named]
                    return '%s' % (val,)

            if'escaped') is not None:
                return self.delimiter
            if'invalid') is not None:
            raise ValueError('Unrecognized named group in pattern',
        return self.pattern.sub(convert, self.template)

def test_templates():
    class P: pass
    p = P() = 'ak'
    txt = 'hello there ${}'
    t = Template(txt)
    assert t.substitute(o=p) == 'hello there ak'

if __name__ == '__main__': test_templates()


More information about the Python-list mailing list