Skip to content

Instantly share code, notes, and snippets.

@wware
Last active August 9, 2016 19:50
Show Gist options
  • Save wware/76ba82d82c2e49a5b01da92ef323799f to your computer and use it in GitHub Desktop.
Save wware/76ba82d82c2e49a5b01da92ef323799f to your computer and use it in GitHub Desktop.
You can write decorators for classes! Who knew? This is a decorator to track the history of what happens to instances of the decorated class following the initial call to their constructor.
import types
import inspect
import pprint
from collections import defaultdict
def get_stack(offset=0):
def frame2tuple(frame):
info = inspect.getframeinfo(frame[0])
return (info.filename, info.lineno)
return [frame2tuple(frame) for frame in inspect.stack()[offset+1:]]
def PaperTrail(cls):
class newcls(cls):
def __where__(self):
return [self.__bt_at_init, get_stack(1)]
def __add_call__(self, methodname, x):
if not hasattr(self, "__method_calls"):
self.__method_calls = defaultdict(list)
self.__method_calls[methodname].append(tuple(x))
def __calls__(self):
return dict(self.__method_calls)
def __new_init__(self, *args, **kwargs):
cls.__init__(self, *args, **kwargs)
self._newcls__bt_at_init = get_stack(1)
setattr(newcls, "__init__", types.MethodType(
__new_init__, None, newcls)
)
def method_renovator(methodname, method):
def newmethod(self, *args, **kwargs):
x = [get_stack(1), args, kwargs]
r = method(self, *args, **kwargs)
x.append(r)
self.__add_call__(methodname, x)
return r
return newmethod
for methodname in (x for x in dir(cls) if not x.startswith("__")):
method = getattr(cls, methodname)
if callable(method):
method = types.MethodType(
method_renovator(methodname, method), None, newcls)
setattr(newcls, methodname, method)
return newcls
@PaperTrail
class DataThingy(object):
def __init__(self, x):
self._x = x
def x(self, *ignored):
return self._x
if __name__ == "__main__":
thingy = DataThingy(3)
assert thingy._x == 3
assert thingy.x('a', 'b', 1234) == 3
assert thingy.__where__() == [
[('papertrail.py', 61)],
[('papertrail.py', 64)]
]
assert thingy.__calls__() == {
'x': [
([('papertrail.py', 63)], # where x() was called
('a', 'b', 1234), # args
{}, # kwargs
3) # return value
]
}, thingy.__calls__()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment