partial string formatting

Posted on

Question :

partial string formatting

Is it possible to do partial string formatting with the advanced string formatting methods, similar to the string template safe_substitute() function?

For example:

s = '{foo} {bar}'
s.format(foo='FOO') #Problem: raises KeyError 'bar'
Asked By: P3trus


Answer #1:

You can trick it into partial formatting by overwriting the mapping:

import string

class FormatDict(dict):
    def __missing__(self, key):
        return "{" + key + "}"

s = '{foo} {bar}'
formatter = string.Formatter()
mapping = FormatDict(foo='FOO')
print(formatter.vformat(s, (), mapping))


FOO {bar}

Of course this basic implementation only works correctly for basic cases.

Answered By: Sven Marnach

Answer #2:

If you know in what order you’re formatting things:

s = '{foo} {{bar}}'

Use it like this:

ss = s.format(foo='FOO') 
print ss 
>>> 'FOO {bar}'

print ss.format(bar='BAR')
>>> 'FOO BAR'

You can’t specify foo and bar at the same time – you have to do it sequentially.

Answered By: aaren

Answer #3:

You could use the partial function from functools which is short, most readable and also describes the coder’s intention:

from functools import partial

s = partial("{foo} {bar}".format, foo="FOO")
print s(bar="BAR")
Answered By: Saikiran Yerram

Answer #4:

This limitation of .format() – the inability to do partial substitutions – has been bugging me.

After evaluating writing a custom Formatter class as described in many answers here and even considering using third-party packages such as lazy_format, I discovered a much simpler inbuilt solution: Template strings

It provides similar functionality but also provides partial substitution thorough safe_substitute() method. The template strings need to have a $ prefix (which feels a bit weird – but the overall solution I think is better).

import string
template = string.Template('${x} ${y}')
  template.substitute({'x':1}) # raises KeyError
except KeyError:

# but the following raises no error
partial_str = template.safe_substitute({'x':1}) # no error

# partial_str now contains a string with partial substitution
partial_template = string.Template(partial_str)
substituted_str = partial_template.safe_substitute({'y':2}) # no error
print substituted_str # prints '12'

Formed a convenience wrapper based on this:

class StringTemplate(object):
    def __init__(self, template):
        self.template = string.Template(template)
        self.partial_substituted_str = None

    def __repr__(self):
        return self.template.safe_substitute()

    def format(self, *args, **kws):
        self.partial_substituted_str = self.template.safe_substitute(*args, **kws)
        self.template = string.Template(self.partial_substituted_str)
        return self.__repr__()

>>> s = StringTemplate('${x}${y}')
>>> s
>>> s.format(x=1)
>>> s.format({'y':2})
>>> print s

Similarly a wrapper based on Sven’s answer which uses the default string formatting:

class StringTemplate(object):
    class FormatDict(dict):
        def __missing__(self, key):
            return "{" + key + "}"

    def __init__(self, template):
        self.substituted_str = template
        self.formatter = string.Formatter()

    def __repr__(self):
        return self.substituted_str

    def format(self, *args, **kwargs):
        mapping = StringTemplate.FormatDict(*args, **kwargs)
        self.substituted_str = self.formatter.vformat(self.substituted_str, (), mapping)
Answered By: Mohan Raj

Answer #5:

Not sure if this is ok as a quick workaround, but how about

s = '{foo} {bar}'
s.format(foo='FOO', bar='{bar}')

? 🙂

Answered By: Memphis

Answer #6:

If you define your own Formatter which overrides the get_value method, you could use that to map undefined field names to whatever you wanted:

For instance, you could map bar to "{bar}" if bar isn’t in the kwargs.

However, that requires using the format() method of your Formatter object, not the string’s format() method.

Answered By: Amber

Answer #7:

>>> 'fd:{uid}:{{topic_id}}'.format(uid=123)

Try this out.

Answered By: Pengfei.X

Answer #8:

Thanks to Amber‘s comment, I came up with this:

import string

    # Python 3
    from _string import formatter_field_name_split
except ImportError:
    formatter_field_name_split = str._formatter_field_name_split

class PartialFormatter(string.Formatter):
    def get_field(self, field_name, args, kwargs):
            val = super(PartialFormatter, self).get_field(field_name, args, kwargs)
        except (IndexError, KeyError, AttributeError):
            first, _ = formatter_field_name_split(field_name)
            val = '{' + field_name + '}', first
        return val
Answered By: gatto

Leave a Reply

Your email address will not be published.