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

A

Alia Khouri

Hi folks,

I've been using ironpython2.7 in a project, and I was generating some
csharp code when i discovered that I couldn't use use str.format
because the interference with the brackets-aplenty situation in
csharp.

In [1]: code = "class {0}Model { public bool IsModel(){ return
true; } }"

In [2]: code.format('My')
---------------------------------------------------------------------------
KeyError Traceback (most recent call
last)

KeyError: ' public bool IsModel(){ return true; } '

Please note I had no trouble with the trusty old % interpolation
method.

Now given that using str.format in this use case (code generation) is
equally difficult for other bracketed languages, and that str.format
is supposed to eventually become 'best-practice' for python string
formatting, can I get some feedback on a proposal that for the odd
case when brackets don't cut it, there is a way to redefine the
delimiter for str.format so that one could do something like this:

str.format_delimiter = ("[[", "]]")

and we could write something like this:

In [1]: code = "class [[0]]Model { public bool IsModel(){ return
true; } }"
In [2]: code.format('My')
Out[3]: 'class MyModel { public bool IsModel(){ return true; } }'


Please give your +N or -N and reasons below.

AK
 
A

Alia Khouri

Roll your ownhttp://docs.python.org/library/string.html#string.Formatter

(-:

Thanks for the tip, looks I can subclass string.Template to get what I
need.

Kicking myself for posting too early...

AK
 
T

Terry Reedy

Hi folks,

I've been using ironpython2.7 in a project, and I was generating some
csharp code when i discovered that I couldn't use use str.format
because the interference with the brackets-aplenty situation in
csharp.

In [1]: code = "class {0}Model { public bool IsModel(){ return
true; } }"

In [2]: code.format('My')

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

Alia Khouri

Terry said:
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>
 
P

Peter Otten

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

You could automatically convert from a custom format like that in your
original post:

import re

_lookup = {
"[[": "{",
"]]": "}",
"{": "{{",
"}": "}}",
}

def _substitute(m):
return _lookup[m.group()]

def custom_format(template, *args, **kw):
return (re.compile(r"\[\[|]]|\{|\}")
.sub(_substitute, template)
.format(*args, **kw))

code = "class [[0]]Model { public bool IsModel(){ return a[42] || true; } }"
print custom_format(code, "My")
 
A

Alia Khouri

Peter said:
You could automatically convert from a custom format like that in your
original post:

import re

_lookup = {
    "[[": "{",
    "]]": "}",
    "{": "{{",
    "}": "}}",

}

def _substitute(m):
    return _lookup[m.group()]

def custom_format(template, *args, **kw):
    return (re.compile(r"\[\[|]]|\{|\}")
            .sub(_substitute, template)
            .format(*args, **kw))

code = "class [[0]]Model { public bool IsModel(){ return a[42] || true;} }"
print custom_format(code, "My")

Nice! I didn't think of that. I guess I could get some additional
performance by taking the re.compile step out of the function. Thanks
for the tip!

AK

AK
 
A

Alia Khouri

Peter said:
You could automatically convert from a custom format like that in your
original post...

<snip>

Here's a class wrapping your functionality:

import re

class Template(object):
'''uses double brackets e.g [[ob.attr]] as delims to get
around curly bracket ({}) collisions when generating code
'''
_pattern = re.compile(r"\[\[|]]|\{|\}")
_lookup = {
"[[": "{",
"]]": "}",
"{": "{{",
"}": "}}",
}
def __init__(self, txt):
self.txt = txt
self.type = type

def _substitute(self, m):
return self._lookup[m.group()]

def render(self, *args, **kwds):
return self._pattern.sub(
self._substitute, self.txt).format(*args, **kwds)

def test_Template():
class P: pass
p = P()
p.name = 'peter'
txt = 'hello there [[o.name]]'
t = Template(txt)
assert t.render(o=p) == 'hello there peter'
 
N

Neil Cerutti

Nice! I didn't think of that. I guess I could get some
additional performance by taking the re.compile step out of the
function. Thanks for the tip!

I recommend "The Pragmatic Programmer," (Hunt, Thomas) ISBN
978-0201616224 and "The Practice of Programming," (Kernighan,
Pike) ISBN 978-0201615869 for more practical tips like this one.
As a mostly self-taught programmer, both books helped me
immensely. If you're a seasoned pro with a CS degree, you might
not find them quite as interesting, though. The second book,
especially, does not cover issues relating to large scale
software development.

As for factoring out re.compile, I believe they are cached by the
re module, so you would save the cost of retrieving the cached
regex, but not the cost of building it.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,995
Messages
2,570,230
Members
46,819
Latest member
masterdaster

Latest Threads

Top