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!

+3


source to share


6 answers


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}'

      

+6


source


You can replace the condition with yourself:

s.format(table_name='users', condition='{condition}')

      

which gives us:



SELECT * FROM users WHERE {condition}

      

You can use this line later to fill out the condition.

+6


source


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}

      

+1


source


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

.

+1


source


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

      

0


source


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}

      

0


source







All Articles