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