13

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?

0

4 Answers 4

10

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.

Sign up to request clarification or add additional context in comments.

1 Comment

stuf is a dict subclass that is attribute-accessible. See link to full module description.
2

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'

Comments

0

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}}

1 Comment

Because the dot is reserved in string formatting, you must use another separator.
0

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!

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.