Python: Missing arguments in string formatting (lazy eval)
I'm trying to do some "post" / "lazy" evaluation of the arguments in my strings. Suppose I have this:
s = "SELECT * FROM {table_name} WHERE {condition}"
I want to return the replacement string {table_name}
, but not {condition}
, so something like this:
s1 = s.format(table_name = "users")
So, I can plot the hole string later, for example:
final = s1.format(condition= "user.id = {id}".format(id=2))
The result should be, of course:
"SELECT * FROM users WHERE user.id = 2"
I found this previous answer, this is exactly what I need, but I would like to use a string function format
.
python format string Thanks for the help!
source to share
You cannot use the format function because it will raise a KeyError.
string.Template
Supports Safe Replacement:
from string import Template
s = Template('SELECT * FROM $table_name WHERE $condition')
s.safe_substitute(table_name='users')
'SELECT * FROM users WHERE $condition'
If you are using simple variable names (no format specifiers, no indexing, etc.) this will work as well (thanks @Simeon Visser for the idea):
def myformat(s, *args, **kwargs):
while True:
try:
return s.format(*args, **kwargs)
except KeyError as e:
e=e.args[0]
kwargs[e] = "{%s}" % e
s = "SELECT * FROM {table_name} WHERE {condition}"
myformat(s, table_name="users")
'SELECT * FROM users WHERE {condition}'
source to share
This builds on @Karoly Horvath's answer to add support for index keys and attribute access by named keys:
import re
def my_format(template, *args, **kwargs):
next_index = len(args)
while True:
try:
return template.format(*args, **kwargs)
except KeyError as e:
key = e.args[0]
finder = '\{' + key + '.*?\}'
template = re.sub(finder, '{\g<0>}', template)
except IndexError as e:
args = args + ('{' + str(next_index) + '}',)
next_index += 1
So, to test this:
class MyObj:
bar = 'baz'
def __repr__(self):
return '<MyObj instance>'
my_obj = MyObj()
template = '{0}, {1}, {foo}, {foo.bar}, {0}, {10}, {missing}'
print my_format(template)
print my_format(template, '1st', '2nd', missing='Not Missing')
print my_format(template, foo=my_obj)
Output:
{0}, {1}, {foo}, {foo.bar}, {0}, {10}, {missing}
1st, 2nd, {foo}, {foo.bar}, 1st, {10}, Not Missing
{0}, {1}, <MyObj instance>, baz, {0}, {10}, {missing}
source to share
I've been using this function for a while now, resulting in Dict
injecting keyword arguments as an object SafeDict
that it subclasses Dict
.
def safeformat(str, **kwargs):
class SafeDict(dict):
def __missing__(self, key):
return '{' + key + '}'
replacements = SafeDict(**kwargs)
return str.format_map(replacements)
I haven't done this, but I find it a good solution. The only downside is that you cannot make a call mystring.safeformat(**kwargs)
- of course you have to call safeformat(mystring,**kwargs)
.
If you're really interested in being able to call mystr.safeformat(**kwargs)
(which I'm interested in doing!), Consider this:
class safestr(str):
def safeformat(self, **kwargs):
class SafeDict(dict):
def __missing__(self, key):
return '{' + key + '}'
replacements = SafeDict(**kwargs)
return safestr(self.format_map(replacements))
Then you can create an object safestr
like a = safestr(mystr)
(for some str
, called mystr
) and you can actually call
mystr.safeformat(**kwargs)
.
eg.
mysafestr = safestr('Hey, {friendname}. I am {myname}.')
print(mysafestr.safeformat(friendname='Bill'))
prints
Hey, Bill. I am {myname}.
This is cool in some cases - you can get around the partially formatted one safestr
and can call safeformat
in different contexts. I especially like to name mystr.format(**locals())
for formatting with appropriate namespace variables; the method is safeformat
especially useful in this case because I don't always look carefully at my namespace.
The main problem is that inherited methods from str
return an object str
, not safestr
. Thus, it mysafestr.lower().safeformat(**kwargs)
fails. You can of course use safestr
when using safeformat
:
safestr(mysafestr.lower()).safeformat(**kwargs)
,
but this is less than a perfect view. I want Python to just give method str
a safeformat
.
source to share
This is a small change in @ ShawnFumo's answer that has a small bug. We need to add word boundary checking (\ b in regex) to make sure we are only matching the failed key and another key starting on the same line. This prevents missing {foo} keys from treating {food} and {foolish} as if they were missing.
import re
def my_format(template, *args, **kwargs):
next_index = len(args)
while True:
try:
return template.format(*args, **kwargs)
except KeyError as e:
key = e.args[0]
finder = r'\{' + key + r'\b.*?\}'
template = re.sub(finder, r'{\g<0>}', template)
except IndexError as e:
args = args + ('{' + str(next_index) + '}',)
next_index += 1
So, to test this:
class MyObj:
bar = 'baz'
def __repr__(self):
return '<MyObj instance>'
my_obj = MyObj()
template = '{0}, {1}, {foo}, {foo.bar}, {0}, {10}, {missing}'
print my_format(template)
print my_format(template, '1st', '2nd', missing='Not Missing')
print my_format(template, foo=my_obj)
print
template2 = '{foo} and {food}'
print my_format(template2)
print my_format(template2, food='burger')
print my_format(template2, foo=my_obj, food='burger')
Output:
{0}, {1}, {foo}, {foo.bar}, {0}, {10}, {missing}
1st, 2nd, {foo}, {foo.bar}, 1st, {10}, Not Missing
{0}, {1}, <MyObj instance>, baz, {0}, {10}, {missing}
{foo} and {food}
{foo} and burger
repr(<MyObj instance>) and burger
source to share
An alternative string.Template.safe_substitute
would be to subclassify string.Formatter
as follows:
class LazyFormatter(string.Formatter):
def get_value(self, key, args, kwargs):
'''Overrides string.Formatter.get_value'''
if isinstance(key, (int, long)):
return args[key]
else:
return kwargs.get(key, '{{{0}}}'.format(key))
lazyfmt = LazyFormatter()
print lazyfmt.format("{field}: {value}", **{'field': 'foo'})
Output:
foo: {value}
source to share