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
Related
I'm trying to create wrapper. My code works with simple example function, but when I apply it to example function, it stops working.
Code of my wrapper:
import functools
import time
from functools import wraps
def profiler(func):
#wraps(func)
def wrapper(*args, **kwds):
setattr(func, 'calls', 0)
t1 = time.monotonic() # what's time at the beginning of run
func(*args, **kwds)
func.calls += 1 # add 1 to number of calls
t2 = time.monotonic() # what's time at the beginning of run
last_time_taken = t2 - t1
setattr(wrapper, 'last_time_taken', last_time_taken)
setattr(wrapper, 'calls', func.calls)
return wrapper
I would be glad to get any advice helping to solve this error. Thanx!
Your wrapper doesn't return, which is the same as returning None. When you call the decorated function, you will in practice just run the wrapper.
Make sure the wrapper returns the result of your function and it will work just fine.
import functools
import time
from functools import wraps
def profiler(func):
#wraps(func)
def wrapper(*args, **kwds):
setattr(func, 'calls', 0)
t1 = time.monotonic() # what's time at the beginning of run
res = func(*args, **kwds)
func.calls += 1 # add 1 to number of calls
t2 = time.monotonic() # what's time at the beginning of run
last_time_taken = t2 - t1
setattr(wrapper, 'last_time_taken', last_time_taken)
setattr(wrapper, 'calls', func.calls)
return res
return wrapper
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
I know the title is a little bit confusing.
So let me take this code as example:
import timeit
def test1(x,y):
return x*y
def time_cost(func):
start = timeit.default_timer()
func()
stop = timeit.default_timer()
print(stop - start)
time_cost(test1)
I want to give test1's two parameter, x and y, to time_cost function.
But I don't know how to do so.
I have tried this before:
import timeit
def test1(x,y):
return x*y
def time_cost(func):
start = timeit.default_timer()
func
stop = timeit.default_timer()
print(stop - start)
time_cost(test1(1,2))
It can run but the result is so weird that I believe this is wrong.
So how to do this? Thank you all.
Return a function that does what you want.
def time_cost(func):
def run(*args, **kwargs):
start = timeit.default_timer()
func(*args, **kwargs)
stop = timeit.default_timer()
print(stop - start)
return run
Then call it.
time_cost(test1)(1, 2)
Give this a try.
from functools import wraps
import timeit
def time_cost(func):
#wraps(func)
def wrapper(*args, **kwargs):
start = timeit.default_timer()
result = func(*args, **kwargs)
stop = timeit.default_timer()
print(stop - start)
return result
return wrapper
test1 = time_cost(test1)
test1(1,2)
This kind of function is called a decorator. Also called a wrapper, or a closure.
You can also use this with the decorator syntax:
#time_cost
def test1(x,y):
return x*y
test1(1,2)
This does the same as the code above.
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