Context:
I’d like to be able to decorate functions so that I can track their stats. Using this post as a reference I went about trying to make my own callable decorator objects.
Here is what I ended up with:
def Stats(fn):
Class StatsObject(object):
def __init__(self, fn):
self.fn = fn
self.stats = {}
def __call__(self, obj, *args, **kwargs):
self.stats['times_called'] = self.stats.get('times_called', 0) + 1
return self.fn(obj, *args, **kwargs)
function = StatsObject(fn)
def wrapper(self, *args **kwargs):
return function(self, *args, **kwargs)
return wrapper
Class MockClass(object):
@Stats
def mock_fn(self, *args, **kwargs):
# do things
Problem:
This actually calls the mock_fn function correctly but I don’t have a reference to the stats object outside the wrapper function. i.e. I can’t do:
mc = MockClass()
mc.mock_fn()
mc.mock_fn.stats
# HasNoAttribute Exception
Then I tried changing the following code recognizing that it was a scoping issue:
From:
function = StatsObject(fn)
def wrapper(self, *args **kwargs):
return function(self, *args, **kwargs)
return wrapper
To:
function = StatsObject(fn)
return function
But of course I lost the self reference (self becomes the StatsObject instance, obj becomes the first arg, and the MockClass object self reference gets lost).
So I understand why the first issue is happening, but not the second. Is there any way that I can pass the self reference of MockClass to the StatsObject __call__ function?
Functions can actually themselves have attributes in Python.
The key line is assigning the
StatsObjectinstance (which you’ve, perhaps misleadingly, locally namedfunction) as an attribute of the function which you return from the decorator.Once you do this,
self.mock_fn.stats_fn.stats(notself.mock_fn()! The attribute is on the function, not its return value) will work within an instance ofMockClass, andMockClass.mock_fn.stats_fn.statswill be available outside. The statistics will be global across all instances ofMockClass(since the decorator is called once, not once per instance), which may or may not be what you want.