You can create a subclass of Dict, which has a substitution parameter, and do the .format directly on __getitem__ - Then the Python engine itself generates the formatting on each string you’re going to use, and you don’t even need to have a "dot in the program where you format the strings" - they’ll just appear formatted in the substitution. The point of this approach is that if you exchange the contents of the dictionary "information" - as is the same object referenced within the modified dictionary, all new readings will be done with the new information.
A simple version can inherit from Dict, make a recursive substitution in creation, treating lists as a special case:
class AutoFormatDict(dict):
def __init__(self, original_dict, format_data):
for key, value in original_dict.items():
if isinstance(value, dict):
original_dict[key] = AutoFormatDict(value, format_data)
elif isinstance(value, list):
for position, list_value in enumerate(value):
if isinstance(list_value, dict):
value[position] = AutoFormatDict(list_value, format_data)
super().__init__(original_dict)
self.format_data = format_data
def __getitem__(self, key):
value = super().__getitem__(key)
return value.format(**self.format_data) if isinstance(value, str) else value
And in the terminal, given the two dictionaries that you present in the question, it works like this:
In [24]: data = AutoFormatDict(dicionario, informacoes)
In [25]: data["infos"][0]["nome"]
Out[25]: 'Joao'
In [26]: informacoes
Out[26]: {'nome_usuario': 'Joao'}
In [27]: informacoes["nome_usuario"] = "Pedro"
In [28]: data["infos"][0]["nome"]
Out[28]: 'Pedro'
Sophisticated version and "Production ready"
A somewhat more sophisticated implementation can make the recursive substitution read-only - within the "getitem" and can inherit from "Collections.abc.Sequence" instead of "Dict" - this ensures that the substitution will work for all the methods of reading a dictionary (iteration with "for", "get" methods, "setdefault", "pop" and etc...). This approach needs a specialized companion class
the list also - since the "getitem" method of the list is separated.
This version can be considered "advanced" because it uses
the classes of "Collections.abc" - but it’s good enough
to be placed in a production code in any application,
or published in a Pypi library.
(And of course, since we’re sophisticated, we can make another auxiliary class -- so that we don’t have an error in the strings that were missing in the substitution)
So inheriting from collections.abc.MutableMapping and collections.abc.MutableSequence. Like most of the
methods will do the same thing - simply pass
the parameters received for the internal "date" structure,
we can refactor this to a base class -
and we just need to implement the insert and the __iter__
separately.
from collections import defaultdict
from collections.abc import MutableMapping, MutableSequence, Sequence, Mapping
class _AutoFormatMixin:
def __init__(self, template_data, subst_data):
self.data = template_data
self.substitution = defaultdict(lambda: '', subst_data)
def __len__(self):
return len(self.data)
def __setitem__(self, key, value):
self.data[key] = value
def __delitem__(self, key):
del self.data[key]
def __getitem__(self, key):
value = self.data[key]
if isinstance(value, Mapping):
return AutoFormatDict(value, self.substitution)
elif isinstance(value, str):
return value.format_map(self.substitution)
elif isinstance(value, Sequence) and not isinstance(value, (bytes, bytearray)):
return AutoFormatList(value, self.substitution)
return value
def __repr__(self):
return f"{self.__class__.__name__}({self.data!r}, {self.substitution!r})"
class AutoFormatDict(_AutoFormatMixin, MutableMapping):
def __iter__(self):
return iter(self.data)
class AutoFormatList(_AutoFormatMixin, MutableSequence):
def insert(self, index, value):
self.data.insert(index, value)
And working:
In [83]: data = AutoFormatDict(dicionario, informacoes)
In [84]: data.substitution["nome_usuario"] = "Beatriz"
In [85]: data["infos"][0]["nome"]
Out[85]: 'Beatriz'
In [86]: data.substitution["nome_usuario"] = "Bruno"
In [87]: data["infos"][0]["nome"]
Out[87]: 'Bruno'
Thank you very much! This solution suits me perfectly. I didn’t know this structure Template, I will read more about it.
– user20273