Ну во-первых, ты вот так сходу это не реализуешь, потому что помимо вложенных словарей есть списки и другие коллекции, а также есть классы. Ты, конечно, можешь схитрить - сделать класс-прокси, который сигнализирует об изменениях при __setattr__() и __setitem()__, а также реализует вызовы __getattr__() и __getitem__() так, чтобы возвращать такой же прокси для целевого объекта.
Условно:
l = [1]
d = {'a': l}
pd = MyProxy(d)
lst = pd['a'] # на самом деле lst это MyProxy(l, owner=pd)
lst[0] = 2 # прокси-список реагирует на присваивание и оповещает владлеьца - прокси-словарь
Но и это не очень хорошо, так как в примере выше я могу изменить список через ссылку l. Не говоря уже о проблемах с проверками типов данных. Так что сложно-составные структуры персистентными сделать малореально.
Я бы сказал, вообще не очень хорошая идея реализовывать персистентность вот так.
Вариант А: делай снапшоты в фиксированные моменты времени. Если сохранение снапшота слишком долгое, сделай клон через deepcopy и сохраняй клон, пока оригинал изменяется дальше.
Вариант Б: явно сохраняй изменения, которые должны быть персистентными. Каждое в отдельности.
Вариант В: Используй
память, отображаемую на диск. Тогда ОС сама будет периодически сбрасывать изменившиеся страницы памяти на диск (хотя есть метод flush()).
Но в этом случае может потребоваться изменить структуру данных, избавившись по максимуму от сложных объектов типа словарей и заменив их на простые массивы байт. Если нужны структуры, то их придётся распаковывать с помощью struct. Т.е. опять приходим к тому, что структура не должна быть сложной.