I guess you want to change the minimal amount of code to your existing program. It could be as simple as using a subclass of
dict
for the variable that you are observing. For example with the
MyDict
class below, all the changes to the dictionary will trigger a printed message. You don't need to change anything to the existing code. Just inject the MyDict object where it is needed.
You can then update a GUI instead of printing messages.
__version__ = '2020.06.21'
sentinel = object()
class Observer:
def on_item_added(self, k, v):
print('added item', k, v)
def on_value_changed(self, k, v, old):
print('value changed', k, v, old)
def on_item_removed(self, k, v):
print('item removed', k, v)
def on_cleared(self):
print('dict cleared')
class MyDict(dict, Observer):
def __setitem__(self, k, v):
w = super().get(k, sentinel)
super().__setitem__(k, v)
if w is sentinel:
self.on_item_added(k, v)
else:
self.on_value_changed(k, v, w)
def __delitem__(self, k):
self.pop(k)
def clear(self):
super().clear()
self.on_cleared()
def pop(self, k, d=sentinel):
try:
v = super().pop(k)
except ValueError:
if d is sentinel:
raise
else:
return d
else:
self.on_item_removed(k, v)
return v
def popitem(self):
k, v = super().popitem()
self.on_item_removed(k, v)
return (k, v)
def update(self, *args, **kwargs):
if args:
if len(args) != 1:
raise TypeError(
'update expected at most 1 argument, got', len(args))
E = args[0]
if hasattr(E, 'keys'):
for k in E:
self[k] = E[k]
else:
for k, v in E:
self[k] = v
for k in kwargs:
self[k] = kwargs[k]
def setdefault(self, k, d=None):
if k not in self:
self[k] = d
return self[k]
if __name__ == '__main__':
d = MyDict()
# This code is exactly the same as in an ordinary dictionary.
d[3] = "foo"
d.update({4:'bar', 5:'spam'})
del d[4]
d.clear()
d.setdefault(8, 'eggs')
d[8] = 9
Output:
added item 3 foo
added item 4 bar
added item 5 spam
item removed 4 bar
dict cleared
added item 8 eggs
value changed 8 9 eggs