I'm trying to make a class that behaves like a dictionary, except any time one of its methods is called or one of its attributes is accessed the fact is logged. I'll clarify what I mean by showing the naive implementation I made (repetitive code is replaced with ellipsis):
class logdict(dict):
def __init__(self, *args, **kwargs):
self._log = [
{'name': '__init__',
'args': tuple(map(repr, args)),
'kwargs': dict((key, repr(kwargs[key])) for key in kwargs)
}
]
return super().__init__(*args, **kwargs)
def __getitem__(self, key):
self._log.append({
'name': '__getitem__',
'args': (repr(key),),
'kwargs': {}
})
return super().__getitem__(key)
def __setitem__(self, key, value):
...
def __delitem__(self, key):
...
def __getattribute__(self, name):
if name == '_log': #avoiding infinite recursion
return super().__getattribute__(name)
...
def __contains__(self, key):
...
def logrepr(self):
log = ''
for logitem in self._log: #this is just formatting, nothing interesting here
log += '{fun}({rargs}{optsep}{rkwargs})\n'.format(
fun = logitem['name'],
rargs = ', '.join(logitem['args']),
optsep = ', ' if len(logitem['kwargs'])>0 else '',
rkwargs = ', '.join('{} = {}'.format(key, logitem['kwargs'][key])
for key in logitem['kwargs'])
)
return log
here, at least for the methods I overloaded, I'm saving which method is being called and the repr of its arguments (if I just saved the arguments, I run the risk of seeing the latest "version" of a mutable object instead of the old one). This implementation kinda works:
d = logdict()
d['1'] = 3
d['1'] += .5
print('1' in d)
print('log:')
print(d.logrepr())
produces:
True
log:
__init__()
__setitem__('1', 3)
__getitem__('1')
__setitem__('1', 3.5)
__contains__('1')
__getattribute__('logrepr')
however it's rather clunky and I'm never sure if I covered all the possible methods. Is there a more efficient way to do this, ideally that generalizes to any given class (and which wraps and logs all dunder methods, not only visible ones)?
Note: this is not a duplicate of this question, as the problem in it was how to avoid infinite recursion rather than how to automate/simplify the process of writing the derived class.