Jace Browning Jace Browning - 3 months ago 10
Python Question

How can I extract keywords from a Python format string?

I want to provide automatic string formatting in an API such that:

my_api("path/to/{self.category}/{self.name}", ...)


can be replaced with the values of attributes called out in the formatting string.




How do I extract the keyword arguments from a Python format string:

"non-keyword {keyword1} {{escaped brackets}} {} {keyword2}" => 'keyword1', 'keyword2'

Answer

String objects have a internal method, str._formatter_parser() that provides that information:

[fname for _, fname, _, _ in yourstring._formatter_parser() if fname]

The Python 3 equivalent is:

import _string

[fname for _, fname, _, _ in _string.formatter_parser(yourstring) if fname]

or use the string.Formatter() class to encapsulate the difference:

from string import Formatter

formatter = Formatter()
[fname for _, fname, _, _ in formatter.parse(yourstring) if fname]

Demo:

>>> yourstring = "path/to/{self.category}/{self.name}"
>>> [fname for _, fname, _, _ in yourstring._formatter_parser() if fname]
['self.category', 'self.name']
>>> yourstring = "non-keyword {keyword1} {{escaped brackets}} {} {keyword2}"
>>> [fname for _, fname, _, _ in yourstring._formatter_parser() if fname]
['keyword1', 'keyword2']

You can parse those field names further; for that you can use the str.__formatter_field_name_split() method / _string.formatter_field_name_split() function, depending on your Python version. The function returns the first part of the name, the one that'd be looked up on in the arguments passed to str.format(), plus a generator for the rest of the field. The generator yields (is_attribute, name) tuples; is_attribute is true if the next name is to be treated as an attribute, false if it is an item to look up with obj[name]:

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

formatter = Formatter()
{formatter_field_name_split(fname)[0]
 for _, fname, _, _ in formatter.parse(yourstring) if fname}

Demo:

>>> from string import Formatter
>>> formatter = Formatter()
>>> yourstring = "path/to/{self.category}/{self.name}"
>>> {formatter_field_name_split(fname)[0]
...  for _, fname, _, _ in formatter.parse(yourstring) if fname}
set(['self'])