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'
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'
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))
printing
FOO {bar}
Of course this basic implementation only works correctly for basic cases.
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.
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}') try: template.substitute({'x':1}) # raises KeyError except KeyError: pass # 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 '${x}${y}' >>> s.format(x=1) '1${y}' >>> s.format({'y':2}) '12' >>> print s 12
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)
Not sure if this is ok as a quick workaround, but how about
s = '{foo} {bar}' s.format(foo='FOO', bar='{bar}')
? :)
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:
http://docs.python.org/library/string.html#string.Formatter.get_value
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.
>>> 'fd:{uid}:{{topic_id}}'.format(uid=123) 'fd:123:{topic_id}'
Try this out.
You could use the partial
function from functools
which is short, most readable and also describes best the coder's intention:
from functools import partial s = partial("{foo} {bar}".format, foo="FOO") print s(bar="BAR") # FOO BAR
Thanks to Amber's comment, I came up with this:
import string try: # 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): try: 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
For me this was good enough:
>>> ss = 'dfassf {} dfasfae efaef {} fds' >>> nn = ss.format('f1', '{}') >>> nn 'dfassf f1 dfasfae efaef {} fds' >>> n2 = nn.format('whoa') >>> n2 'dfassf f1 dfasfae efaef whoa fds'
There is one more way to achieve this i.e by using format
and %
to replace variables. For example:
>>> s = '{foo} %(bar)s' >>> s = s.format(foo='my_foo') >>> s 'my_foo %(bar)s' >>> s % {'bar': 'my_bar'} 'my_foo my_bar'
Assuming you won't use the string until it's completely filled out, you could do something like this class:
class IncrementalFormatting: def __init__(self, string): self._args = [] self._kwargs = {} self._string = string def add(self, *args, **kwargs): self._args.extend(args) self._kwargs.update(kwargs) def get(self): return self._string.format(*self._args, **self._kwargs)
Example:
template = '#{a}:{}/{}?{c}' message = IncrementalFormatting(template) message.add('abc') message.add('xyz', a=24) message.add(c='lmno') assert message.get() == '#24:abc/xyz?lmno'
You could wrap it in a function that takes default arguments:
def print_foo_bar(foo='', bar=''): s = '{foo} {bar}' return s.format(foo=foo, bar=bar) print_foo_bar(bar='BAR') # ' BAR'