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

Alia Khouri alia_khouri at yahoo.com
Mon Apr 4 11:42:36 EDT 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:
<code>
import re
class _multimap:
 """Helper class for combining multiple mappings.
 Used by .substitute() to combine the mapping and keyword
 arguments.
 """
 def __init__(self, primary, secondary):
 self._primary = primary
 self._secondary = secondary
 def __getitem__(self, key):
 try:
 return self._primary[key]
 except KeyError:
 return self._secondary[key]
class _TemplateMetaclass(type):
 pattern = r"""
 %(delim)s(?:
 (?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
 else:
 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
 else:
 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])
 else:
 mapping = args[0]
 # Helper function for .sub()
 def convert(mo):
 # Check the most common path first.
 named = mo.group('named') or mo.group('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
characters.
 # XXXX here is the probably dangerous eval hack XXXXXX
 if '.' in named:
 return eval(named, kws)
 else:
 val = mapping[named]
 return '%s' % (val,)
 if mo.group('escaped') is not None:
 return self.delimiter
 if mo.group('invalid') is not None:
 self._invalid(mo)
 raise ValueError('Unrecognized named group in pattern',
 self.pattern)
 return self.pattern.sub(convert, self.template)
def test_templates():
 class P: pass
 p = P()
 p.name = 'ak'
 txt = 'hello there ${o.name}'
 t = Template(txt)
 assert t.substitute(o=p) == 'hello there ak'
if __name__ == '__main__': test_templates()
</code>


More information about the Python-list mailing list

AltStyle によって変換されたページ (->オリジナル) /