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.