'Formatting only named fields with str.format_map()

I'm familiar with the usual ways to avoid missing fields causing problems when formatting a string. For example:

class SafeMap(dict):
    def __missing__(self, key):
        return f'{{{key}}}'


s = '{a}{b}'.format_map(SafeMap({'a': 1}))
print(s)

Result:

'1{b}'

However, I have a case where I want to apply a partial map like in the example, but the string also contains positional fields, for example:

s = '{a}{}{b}'.format_map(SafeMap({'a': 1}))
print(s)

Causes a ValueError: Format string contains positional fields due to there not being a value provided to replace the positional field between {a} and {b}.

My question: without manually parsing the entire string myself, is there a reliable way to perform a partial .format_map() on a string that leaves both unmatched named fields and positional fields alone?

(either all of them, or perhaps it only fills the ones that are not provided with a value - but I'm specifically after a case where it simply does not touch positional fields)

That is when running:

s = '{a}{}{b}'.format_map(SomeMagicClass({'a': 1}))

I need the result to be:

1{}{b}

And I need the solution to work reliably for any formattable string, not just this trivial case. If you know of a reliable solution that doesn't use a class, I'd be happy with that as well, the class-based solution is only presented as an example because it is a common solution.



Solution 1:[1]

What I ended up with myself:

from string import Formatter


def partial_format_map(s, d):
    return ''.join(lit + (
        Formatter().convert_field(
            Formatter().format_field(self.globals[key], fmt), conv) if key in self.globals else
        '' if key is None else
        f'{{{key}{":" + fmt if fmt else ""}{":" + conv if conv else ""}}}'
    ) for lit, key, fmt, conv in Formatter().parse(value))

This works fairly well, but I'd be happy to accept a better answer - especially if someone sees fault with this solution.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Grismar