Needing to partially fill format strings is a common problem when progressively filling the format strings, e.g. for SQL queries.
format_partial()
method uses the Formatter
from string
and ast
to parse the format string and also find out whether the named parameter hash has all the values needed to partially evaluate the format:
import ast
from collections import defaultdict
from itertools import chain, ifilter, imap
from operator import itemgetter
import re
from string import Formatter
def format_partial(fstr, **kwargs):
def can_resolve(expr, **kwargs):
walk = chain.from_iterable(imap(ast.iter_fields, ast.walk(ast.parse(expr))))
return all(v in kwargs for k,v in ifilter(lambda (k,v): k=='id', walk))
ostr = fstr
fmtr = Formatter()
dd = defaultdict(int)
fmtr.get_field = lambda field_name, args, kwargs: (dd[field_name],field_name)
fmtr.check_unused_args = lambda used_args, args, kwargs: all(v in dd for v in used_args)
for t in ifilter(itemgetter(1), Formatter().parse(fstr)):
f = '{'+t[1]+(':'+t[2] if t[2] else '')+'}'
dd = defaultdict(int)
fmtr.format(f,**kwargs)
if all(can_resolve(e,**kwargs) for e in dd):
ostr = re.sub(re.escape(f),Formatter().format(f, **kwargs),ostr,count=1)
return ostr
format_partial
will leave the unresolved portion of the format string, so subsequent calls can be used to resolve those parts as the data is available.
goodmami's and dawg's answers seem cleaner, but they both fail to capture the format mini-language completely as in {x:>{x}}
; format_partial
will have no problem resolving any format string that string.format()
resolves:
from datetime import date
format_partial('{x} {} {y[1]:x} {x:>{x}} {z.year}', **{'x':30, 'y':[1,2], 'z':date.today()})
'30 {} 2 30 2016'
It is even easier to extend the functionality to old style format strings using regex instead of the string formatter, as the old style format substrings were regular (ie. no nested markers).