I'm extremely new to python, and i just encountered decorators. I'm still kinda confused by them but i am learning
i was trying to make a decorator that tells me how much time my function took to finish, but apparently when i try to use it on a function that should return something, it just returns "None"
I've seen only a couple of questions talking about this problem but none of them actually helped
Here's my code
import time
def time_it(func): # Here i make a simple decorator function that should time my decorated function
def wrapper(*args, **kwargs):
t1 = time.time()
func(*args)
t2 = time.time()
total = t2 - t1
print("The function '" + func.__name__ + "' took", str(total)[0:5], "seconds to complete")
return wrapper
#time_it
def square(nums): # I make a function that squares every number in a list
new_list = []
for n in nums:
new_list.append(n ** 2)
return new_list
lis = [f for f in range(200000)] # i make a list with a range of 200000
print(square(lis))
sorry for any grammatical errors, i'm not a native english speaker
The problem is that your inner function return value isn't being returned. The change is noted below:
from functools import wraps
def time_it(func): # Here i make a simple decorator function that should time my decorated function
#wraps(func)
def wrapper(*args, **kwargs):
t1 = time.time()
## Note the change on this line -- I now store the return result from the called function
result = func(*args, **kwargs)
t2 = time.time()
total = t2 - t1
print("The function '" + func.__name__ + "' took", str(total)[0:5], "seconds to complete")
## And then explicitly return the result
return result
return wrapper
For the decorator, you need to remember that it's just a closure, with some fancy syntax. You still need to deal with the function return parameters yourself.
A couple of additions:
from functools import wraps and #wraps(func)
this will create wrap the inner function with some details that exist in the wrapping function. There's a small example in the python docs here:
https://docs.python.org/3/library/functools.html
The decorator replaces square with wrapper and wrapper does not return anything. It should return the value returned by the wrapped function.
This is the correct way to do it:
def time_it(func):
def wrapper(*args, **kwargs):
t1 = time.time()
try:
return func(*args, **kwargs)
finally:
t2 = time.time()
total = t2 - t1
print("The function '" + func.__name__ + "' took", str(total)[0:5], "seconds to complete")
return wrapper
I changed 3 things:
added return, so that the value is returned from decorated function
added **kwargs to func calls, because it may be needed if used differently
added try/finally block, so that the printout happens even in case of an exception, plus this makes it easier to return the value.
Your decorated function doesn't return anything explicitely - so, by default, it returns None.
You can capture the output before printing the time, and return at the end:
def time_it(func): # Here i make a simple decorator function that should time my decorated function
def wrapper(*args, **kwargs):
t1 = time.time()
out = func(*args)
t2 = time.time()
total = t2 - t1
print("The function '" + func.__name__ + "' took", str(total)[0:5], "seconds to complete")
return out
return wrapper
Related
I tried to write a decorator to compute the time of the computation for methods in a class, but I also have a lot of properties.
I tried to write a decorator for a property as follows:
def timer(method):
import time
#property
def wrapper(*args, **kw):
start = time.time()
result = method
end = time.time()
print('Elapsed time for: {} is: {}s'.format(method.__name__,(end-start)*1000))
return result
return wrapper
I can't get the name of the property as written, moreover perhaps you would suggest to write it in another way?
You would need to stack decorators:
def timer(method):
import time
def wrapper(*args, **kwargs):
start = time.time()
result = method(*args, **kwargs) # note the function call!
end = time.time()
print('Elapsed time for: {} is: {}s'.format(method.__name__,(end-start)*1000))
return result
return wrapper
class X:
#property
#timer
def some_method(self):
# timed code
return 0
>>> x = X()
>>> x.some_method
Elapsed time for: some_method is: 0.0050067901611328125s
0
I want to create a function that calculates the execution time of other functions, but when I do that, I get an error like: 'int' object is not callable. What is the problem here?
import time
def square(x):
return x**2
def timer(func):
t1 = time.perf_counter()
func()
t2 = time.perf_counter()
print(t2-t1)
timer(square(5))
It is also possible to modify your code to make it work, but you'll have to pass in the arguments of square() into timer() after passing in the function as the first argument:
def timer(func, *args, **kwargs):
t1 = time.perf_counter()
func(*args, **kwargs)
t2 = time.perf_counter()
print(t2-t1)
timer(square, 5)
Using *args* and **kwargs lets us deal with functions with arbitrary parameters.
A more convenient way to do this is to use a decorator. It returns a wrapper function around the original function. You don't have to change much in order to time a particular function. Here's an example:
def timer(func):
def wrapper(*args, **kwargs):
func_name = func.__name__
print(f"Starting {func_name}")
t1 = time.perf_counter()
output = func(*args, **kwargs)
t2 = time.perf_counter()
print(f"Total time for {func_name}: {t2 - t1:.3f} s\n")
return output
return wrapper
To use it, simply do:
#timer
def square(x):
return x**2
square(5)
Or:
def square(x):
return x**2
timed_square = timer(square)
timed_square(5)
Your timer expects a function to call, but you're giving it the result of already calling one (and that result isn't a function).
You can do timer(lambda: square(5)) instead. Then it's your timer function that executes the (anonymous) function and thus the expression square(5) as intended.
square(5) return 25; so you're trying to run your timer on a number ;)
Try instead:
import time
def timer(func):
def f(args):
t1 = time.perf_counter()
ret = func(args)
t2 = time.perf_counter()
print('execution time: {}'.format(t2-t1))
return ret
return f
def square(x):
return x**2
timedSqure = timer(square)
res = timedSqure(5)
print(res)
Further, I recommend on learning decorators in python, because with decorators you can make it even more elegant by declaring:
#timer
def square(x):
return x**2
See repl here
And last, per #Heap Overflow's comment: it doesn't make sense to time something that runs so quickly. If you want to benchmark a function you should use timeit
Could you please let me know if there is a way for a decorated function to keep its metadata?
This would be the code for the decorator:
def timer(func):
"""prints how long a function takes to run."""
def wrapper(*args, **kwargs):
t_start = time.time()
result = functionalists(*args, **kwargs)
t_total = time.time() - t_start
print('{} took {}s'.format(functionalists.__name__, t_total))
return result
return wrapper
The following would be the decorated function.
#timer
def sleep_n_seconds(n=10):
"""pause processing for n seconds.
Args:
n (int): The number of seconds to pause for.
"""
time.sleep(n)
When I try to print the docstrings with the following code, the metadata is not returned.
print(sleep_n_seconds.__doc__)
Please let me know if I need to provide further details.
Thank you
Use the wraps function from functools module to retain the signature. :
from functools import wraps
def timer(func):
#wraps(func)
"""prints how long a function takes to run."""
def wrapper(*args, **kwargs):
t_start = time.time()
result = functionalists(*args, **kwargs)
t_total = time.time() - t_start
print('{} took {}s'.format(functionalists.__name__, t_total))
return result
return wrapper
This question already has answers here:
Read/Write Python Closures
(8 answers)
Closed 8 years ago.
I'm trying to write a decorator in Python to limit the number of times a function is called in an amount of time. I anticipate using it like this:
#ratelimit(seconds=15)
def foo():
print 'hello'
start = time.time()
while time.time() - start < 10:
foo()
> 'hello'
> 'hello'
So the decorated function can be called a maximum of once every seconds. In terms of implementing it I have this, but it doesn't work as I'm not sure the correct way to persist the last_call between subsequent calls:
import time
def ratelimit(seconds=10):
last_call = None # Never call decorated function
def decorator(func):
def wrapper(*args, **kwargs):
if last_call is None or time.time() - last_call > seconds:
result = func(*args, **kwargs)
last_call = time.time()
return result
return wraps(func)(wrapper)
return decorator
The code below worked fine for me in python 2.7.
import time
from functools import wraps
last_called = dict() # When last called, and with what result
def ratelimit(seconds=10, timer=time.time):
def decorator(func):
last_called[func] = None
#wraps(func)
def wrapper(*args, **kwargs):
now = timer()
call_data = last_called.get(func, None)
if call_data is None or now - call_data[0] >= seconds:
result = func(*args, **kwargs)
last_called[func] = (now, result)
else:
result = call_data[1] # Replay rate-limited result
return result
return wrapper
return decorator
I'm building a wrapper for an API which demands at least 1 second of waiting between each call. I thought I could solve this using a decorator in the following way:
import datetime, time
last_time = datetime.datetime(2014, 1, 1)
def interval_assurer(f):
global last_time
if (datetime.datetime.now() - last_time).seconds < 1:
print("Too fast...")
time.sleep(1)
last_time = datetime.datetime.now()
return f
#interval_assurer
def post():
pass
This won't work though, for some reason, and I'm not sure why. last_time gets updated the first time the post is called, but won't update afterwards. Please keep in mind that this is the first time I'm experimenting with decorators, so I am probably missing something fundamental.
Thanks.
Since your interval_assurer is a decorator, it's called exactly once: when the function is defined, and not when it's called. You need to create a wrapping function like this:
import time, functools
def interval_assurer(f):
last_time = [0]
#functools.wraps(f) # optional, but nice to have
def wrapper(*args, **kwargs):
time_diff = time.time() - last_time[0]
if time_diff < 1:
print("Too fast...")
time.sleep(1 - time_diff)
last_time[0] = time.time()
return f(*args, **kwargs)
return wrapper
#interval_assurer
def post(self, **kwargs):
pass
You also won't need the global then (the trick with the list can be replaced with nonlocal in Python 3).
The decorator function just returns the original function, so the timing code only runs when the decorator is called, i.e. when the function definition is evaluated. Instead, it should return a new function that incorporates the timing code and calls f when appropriate.
Try something like:
def interval_assurer(f):
def func():
global last_time
if (datetime.datetime.now() - last_time).seconds < 1:
print("Too fast...")
time.sleep(1)
last_time = datetime.datetime.now()
return f()
return func
If your decorated function takes arguments, you should include *args, **kwargs in the definition of func and the call to f; also, consider decorating func in turn with functools.wraps(f).
Building on #bereal's answer, you can make the last_time an attribute of the wrapper function to remove the global (allowing multiple wrapped functions, each with their own timer), and even make a decorator that takes an argument for the interval to enforce:
import functools
import time
def interval_assured(interval):
"""Ensure consecutive calls are separated by a minimal interval."""
def wrapper(f):
#functools.wraps(f)
def func(*args, **kwargs):
if (time.time() - func.last_time) < interval:
time.sleep(interval)
result = f(*args, **kwargs)
func.last_time = time.time()
return result
func.last_time = time.time()
return func
return wrapper
Note that the time is reset after the wrapped function f is called - this is important if the run-time of f is large relative to the interval.
In use:
>>> def testing(x):
print time.time()
print x
>>> for x in range(3):
testing(x)
1405938405.97
0
1405938406.01
1
1405938406.02
2
>>> #interval_assured(5)
def testing(x):
print time.time()
print x
>>> for x in range(3):
testing(x)
1405938429.71
0
1405938434.73
1
1405938439.75
2