I've seen a few similar questions on SO regarding detecting changes to a dictionary and calling a function when the dictionary changes, such as:
These examples use variations of the Observer pattern or overloading __setitem__
, but all these examples don't detect changes on nested dictionary values.
For example, if I have:
my_dict = {'a': {'b': 1}}
my_dict['a']['b'] = 2
The assignment of 2
to the element ['a']['b']
will not be detected.
I'm wondering if there is an elegant way of detecting changes not only to the base elements of a dictionary but all the child elements of a nested dictionary as well.
To access element of a nested dictionary, we use indexing [] syntax in Python.
Accessing Elements from Dictionary Keys can be used either inside square brackets [] or with the get() method. If we use the square brackets [] , KeyError is raised in case a key is not found in the dictionary. On the other hand, the get() method returns None if the key is not found.
Use get() and Key to Check if Value Exists in a Dictionary Dictionaries in Python have a built-in function key() , which returns the value of the given key. At the same time, it would return None if it doesn't exist.
Adding or updating nested dictionary items is easy. Just refer to the item by its key and assign a value. If the key is already present in the dictionary, its value is replaced by the new one. If the key is new, it is added to the dictionary with its value.
Building on the answer given in here, just do the following:
class MyDict(dict):
def __setitem__(self, item, value):
print("You are changing the value of {} to {}!!".format(item, value))
super(MyDict, self).__setitem__(item, value)
and then:
my_dict = MyDict({'a': MyDict({'b': 1})})
my_dict['a']['b'] = 2
You are changing the value of b to 2!!
my_dict['a'] = 5
You are changing the value of a to 5!!
If you want to avoid manual calls to MyDict at each nesting level, one way of doing it, is to fully overload the dict class. For example:
class MyDict(dict):
def __init__(self,initialDict):
for k,v in initialDict.items():
if isinstance(v,dict):
initialDict[k] = MyDict(v)
super().__init__(initialDict)
def __setitem__(self, item, value):
if isinstance(value,dict):
_value = MyDict(value)
else:
_value = value
print("You are changing the value of {} to {}!!".format(item, _value))
super().__setitem__(item, _value)
You can then do the following:
# Simple initialization using a normal dict synthax
my_dict = MyDict({'a': {'b': 1}})
# update example
my_dict['c'] = {'d':{'e':4}}
You are changing the value of c to {'d': {'e': 4}}!!
my_dict['a']['b'] = 2
my_dict['c']['d']['e'] = 6
You are changing the value of b to 2!!
You are changing the value of e to 6!!
Complete solution borrowing from the this link(the second one given by OP)
class MyDict(dict):
def __setitem__(self, item, value):
print("You are changing the value of {key} to {value}!!".format(key=item, value=value))
super(MyDict, self).__setitem__(item, convert_to_MyDict_nested(value))
def convert_to_MyDict_nested(d):
if not(isinstance(d, dict)):
return d
for k, v in d.items():
if isinstance(v, dict):
d[k] = convert_to_MyDict_nested(v)
return MyDict(d)
So that if
d = {'a': {'b': 1}}
then,
d = convert_to_MyDict_nested(d)
d['a']['b'] = 2 # prints You are changing the value of b to 2!!
d['a']= 5 # prints You are changing the value of a to 5!!
Also, edited according to comment by OP. So,
d["c"] = {"e" : 7} # prints You are changing the value of c to {'e': 7}!!
d["c"]["e"] = 9 # prints You are changing the value of e to 9!!
You can write an adapter class that automatically wraps values in itself, like this (untested, but I think it illustrates the point):
class DictChangeListenerAdapter:
def __init__(self, original, listener):
self._original = original
self._listener = listener
def __getitem__(self, key):
value = self._original.__getitem__(key)
if isinstance(value, dict):
return DictChangeListenerAdapter(self._original[key], self._listener)
else:
return value
def __setitem__(self, key, value):
self._original.__setitem__(key, value)
self._listener(self, key, value)
Note that this is going to cause access to the wrapped items to be much more expensive, use with care.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With