Markus Johansson
Markus Johansson

Reputation: 3773

How to use a dot in Python format strings?

I want to format a string and be able to use the dot operator, so that I can construct template strings containing e.g. {user.name}, {product.price}.

I tried this:

'Hello {user.name}'.format( {'user': { 'name': 'Markus' } } )
KeyError: 'user'

'Hello {user.name}'.format( **{'user': { 'name': 'Markus' } } )
AttributeError: 'dict' object has no attribute 'name'

Is there a way to do it?

Upvotes: 13

Views: 12638

Answers (4)

Giles
Giles

Reputation: 1667

There are some good answers here but none quite fitted my use case, I ended up solving mine by reformatting the dot notation string to be suitable for python string.format. Here is the function in case it is of use to anyone else landing here:

import string

def dots_to_brackets(string_in : str) -> str: 
    fieldnames = [fname for _, fname, _, _ in string.Formatter().parse(string_in) if fname] # this bit from https://stackoverflow.com/questions/25996937/how-can-i-extract-keywords-from-a-python-format-string
    string_out = string_in
    for field in fieldnames:
        # replace . with [ and then add a closing ] for each . to the end of the string
        new_field = field.replace(".", "[") + "]" * field.count(".")
        # now replace the field name in the original string with the new field name
        string_out = string_out.replace(field, new_field)
    return string_out

print(dots_to_brackets("{a.b}")) # > {a[b]}
print(string.Formatter().vformat(dots_to_brackets("{a.b}"), (), {'a': {'b':'Success!'}})) # > Success!

Upvotes: 0

Paul Kenjora
Paul Kenjora

Reputation: 2004

Flatten the dictionary using comprehension....

 def flatten_dict(dd, separator='>', prefix=''):
  return {
    prefix + separator + k if prefix else k : v
    for kk, vv in dd.items()
    for k, v in flatten_dict(vv, separator, kk).items()
  } if isinstance(dd, dict) else { prefix : dd }

A call like this:

x = { 'A':1, 'B':{'C':2}}
y = flatten_dict(x)

Produces:

y = { 'A':1, 'B>C':2}}

Upvotes: 0

jonrsharpe
jonrsharpe

Reputation: 121955

The minimal change is to use square brackets in your template, rather than a period:

              # v Note
>>> 'Hello {user[name]}'.format(**{'user': {'name': 'Markus'}})
'Hello Markus'

Alternatively, put objects that actually have that attribute in the dictionary, e.g. a custom class or collections.namedtuple:

>>> class User(object):
    def __init__(self, name):
        self.name = name


>>> 'Hello {user.name}'.format(**{'user': User('Markus')})
'Hello Markus'

Note also that if you're writing out the literal you can just use a keyword argument:

>>> 'Hello {user.name}'.format(user=User('Markus'))
'Hello Markus'

Upvotes: 2

Jonathan Eunice
Jonathan Eunice

Reputation: 22443

Python dict objects are unfortunately not attribute accessible (i.e. with the dot notation) by default. So you can either resign yourself to the uglier brackets notation:

'Hello {user[name]}'.format( **{'user': { 'name': 'Markus' } } )

Or you can wrap your data in a dot-accessible object. There are a handful of attribute-accessible dictionary classes you can install from PyPI, such as stuf.

from stuf import stuf

'Hello {user.name}'.format( **stuf({'user': { 'name': 'Markus' } }) )

I tend to keep my collections in stuf objects so that I can easily access them by attribute.

Upvotes: 10

Related Questions