Reputation: 3773
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
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
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
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
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