Home > Mobile >  How to call a decorator (instance method) of other class?
How to call a decorator (instance method) of other class?

Time:08-24

I have a class called RMath() which takes 2 values a and b. Then there is a function named add() which sums up these a and b. Now i want to record the calculation time. I decided to use decorator here without touching the original function. At the same time, i don't want to write this decorator in the same class, So i have created a new class as Handler, and created an instance method as timeit. But when i am trying to use this decorator as @Handler.timeit, it throws an error TypeError: timeit() missing 1 required positional argument: 'func'

import time

class Handlers():

    def timeit(self, func):
        def timefunc():
            start = time.time()
            func()
            end = time.time()
            print('Time taken: ', end - start)
        return timefunc()

class RMath():
    def __init__(self, a, b):
        self.a = a
        self.b = b

    @Handlers.timeit
    def add(self):
        try:
            self.a   self.b
        except Exception as e:
            return e

a = 10
b = 20
m = RMath(a, b)
c = m.add()
print(c)

At least from the error i could see there is some issue with @Handlers.timeit

Could someone please help me to understand the issue?

Even if i remove self from decorator as def timeit(func): it is throwing error: TypeError: add() missing 1 required positional argument: 'self'

CodePudding user response:

You can use either @classmethod or @staticmethod in this case. Here's how I'd approach it:

from functools import wraps
from time import time


class Handlers:

    @staticmethod
    def timeit(func):

        @wraps(func)
        def timed_func(*args, **kwargs):
            start = time()
            ret = func(*args, **kwargs)
            end = time()
            print(f'[{func.__qualname__}] Time taken: {end - start}')

            return ret

        return timed_func


class RMath:
    def __init__(self, a, b):
        self.a = a
        self.b = b

    @Handlers.timeit
    def add(self):
        try:
            # add a `return` here!
            return self.a   self.b
        except Exception as e:
            return e


a = 10
b = 20
m = RMath(a, b)
c = m.add()
print(c)

Out:

[RMath.add] Time taken: 3.0994415283203125e-06
30

Update: the *args, **kwargs* is a generalized approach to handle a function that takes any number of parameters, however if you explicitly just to want to support the RMath.add() method - a function which takes a single parameter self - you can use a slightly different approach as below:

@wraps(func)
def timed_func(self):
    start = time()
    ret = func(self)
    end = time()
    print(f'[{func.__qualname__}] Time taken: {end - start}')

    return ret

CodePudding user response:

There are many smaller things that needs to be fixed to make this work. You have to return the inner function from your decorator, not the result of calling the function. You also have to marshal the arguments given to the original function through your function - and you need to return the result from your wrapped function after computing the time spent.

And since you never create a Handler instance, you should define the wrapping function as a static method (it'll still work even if you don't do this, but there will be no self).

import time

class Handlers():
    # make sure we don't require an instance
    @staticmethod
    def timeit(func):
        def timefunc(*args, **kwargs):
            #        ^--- needs to accept and marshal the arguments
            start = time.time()
            result = func(*args, **kwargs)
            # ^- keep result ^--- marshal the arguments
            end = time.time()
            print('Time taken: ', end - start)
            return result
            # ^- return the result of calling the wrapped function

        return timefunc
        # ^-- return the function, not the result of calling the function


class RMath():
    def __init__(self, a, b):
        self.a = a
        self.b = b

    @Handlers.timeit
    def add(self):
        try:
            return self.a   self.b
            # ^--- needs to return the result of the addition
        except Exception as e:
            return e

a = 10
b = 20
m = RMath(a, b)
c = m.add()
print(c)

CodePudding user response:

As it's written now, you need a Handlers instance to run Handlers.timeit(), so you need to instantiate the class and use the timeit() method of the instance to decorate the methods in your other classes.

But I think what you're wanting to do is use the class as a namespace to hold methods. In this case you can decorate the methods with @staticmethod. After all, the method doesn't actually use an instance of the class.

class Handlers():

    @staticmethod
    def timeit(func):
        def timefunc():
            start = time.time()
            func()
            end = time.time()
            print('Time taken: ', end - start)
        return timefunc()

Or you could just use it as a plain function outside of any class, or (probably best idea) put it in a separate module that you import.

  • Related