Last active
August 9, 2016 19:50
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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