The str.format()
method doesn't give you a direct method to handle missing keys or replace values.
You can add a layer of indirection; pass in a mapping that handles missing and None
values, and alter the format to use just that argument:
class PlaceholderFormatValue():
def __format__(self, spec):
return '~'
def __getitem__(self, name):
# handle further nested item access
return self
class formatting_dict(dict):
def __getitem__(self, name):
value = self.get(name)
if isinstance(value, dict):
# rewrap nested dictionaries to handle missing nested keys
value = type(self)(value)
return value if value is not None else PlaceholderFormatValue()
print('{0[n]}, {0[k]:.2f}, {0[p][a]}, {0[p][b]}'.format(formatting_dict(data)))
Now all slots refer to positional argument 0
, which is treated like a dictionary, but key lookups always succeed and both missing values and None
are replaced by a placeholder value.
Here the PlaceholderFormatValue()
ensures that regardless of what the format spec gives, the value can be interpolated into the format. This makes {0[k]:.2f}
work, for example.
By wrapping any dict
values and having PlaceholderFormatValue
handle item access, the above can also handle failure to provide nested keys or whole dictionaries:
>>> data = {'n': 3, 'k': 3.141594, 'p': {'a': 7, 'b': 8}}
>>> del data['k']
>>> data['p']['b'] = None
>>> print('{0[n]}, {0[k]:.2f}, {0[p][a]}, {0[p][b]}'.format(formatting_dict(data)))
3, ~, 7, ~
>>> del data['p']['a']
>>> print('{0[n]}, {0[k]:.2f}, {0[p][a]}, {0[p][b]}'.format(formatting_dict(data)))
3, ~, ~, ~
>>> del data['p']
>>> print('{0[n]}, {0[k]:.2f}, {0[p][a]}, {0[p][b]}'.format(formatting_dict(data)))
3, ~, ~, ~