The objective is to count instance method calls with class decorator. I chose the class decorator, because want to store there field call_count separately for every decorated instance method. A function decorator can't solve this problem because either developer should manually add fields call_count to the class with decorated methods or add to the decorator function an attribute field call_count. The first case don't follow OOP paradigm and also there is extra work. At the second case function attribute keep count of all decorated instance methods (multiply amount of instances on amount of decorated methods).
At result I want to get call count like this: instance_name.method_name.call_count
.
I studied similar questions here and tried all of them, but couldn't solve the problem.
A decorator for functions doesn't help, because the decorated method doesn't receive class instance:
class Wrapper(object):
def __init__(self, func):
self.call_count = 0
self.func = func
def __call__(self, *args, **kwargs):
self.call_count = 1
return self.func(*args, **kwargs)
class SomeBase:
def __init__(self):
self._model = " World"
@Wrapper
async def create(self, arg_1, arg_2):
return arg_1 self._model arg_2
async def test_wrapper():
base = SomeBase()
result = await base.create("hello", "!")
await base.create("hello", "!")
assert result == "hello World!"
assert base.create.call_count == 2
And I get an error:
test_call_count.py::test_wrapper FAILED
utils/tests/test_call_count.py:95 (test_wrapper)
async def test_wrapper():
base = SomeBase()
> result = await base.create("hello", "!")
test_call_count.py:98:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <utils.tests.test_call_count.Wrapper object at 0x7f8c10f8b310>
args = ('hello', '!'), kwargs = {}
def __call__(self, *args, **kwargs):
self.call_count = 1
> return self.func(*args, **kwargs)
E TypeError: create() missing 1 required positional argument: 'arg_2'
test_call_count.py:84: TypeError
How to solve this problem?
CodePudding user response:
This can be achieved by overriding the __get__
method, whose first argument contains the instance of the actual class whose method is decorated. If you save this argument, you can reuse it in the __call__
method to pass it as the real method's first argument:
class Wrapper(object):
def __init__(self, func):
self.call_count = 0
self.decorated_instance = None
self.func = func
def __call__(self, *args, **kwargs):
self.call_count = 1
return self.func(self.decorated_instance, *args, **kwargs)
def __get__(self, obj, objtype):
self.decorated_instance = obj
return self