Decorators for recursion function - python

I want to write a decorator that will compute runtime of function that compute fibonachi number of 5(fib(5)) 10 times and will print a medium runtime value. But my decorator returns an error. How to fix this problem?
import functools
import time
def trace(times):
def cache(func):
#functools.wraps(func)
def wrapper(*args, **kwargs):
total = 0
for i in range(times):
start = time.time()
func(*args, **kwargs)
end = time.time()
total += (end - start)
print('Medium value is {}'.format(total / times))
return func
return wrapper
return cache
#trace(times=10)
def fib(num):
if num < 2:
return num
return fib(num - 1) + fib(num - 2)
fib(5)

First, instead of returning func in the wrapper return the result of it.
We have to make sure the tracing only happens for the first call to fib and none of the recursive calls. Let's use a boolean traced argument:
import functools
import time
def trace(times):
def cache(func):
#functools.wraps(func)
def wrapper(*args, traced=True, **kwargs):
if not traced:
return func(*args, **kwargs)
total = 0
for i in range(times):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
total += (end - start)
print(f'run {i}, time is {total}')
print(f'Mean value is {total / times}')
return result
return wrapper
return cache
#trace(times=10)
def fib(num, traced=True):
if num < 2:
return num
return fib(num - 1, traced=False) + fib(num - 2, traced=False)
fib(5)
traced is True by default so any call to fib(n) will be traced (no need for user to be aware of the traced argument). However the recursive calls all have traced=False therefore they don't trigger the loop in the decorator.

Related

How to you get the information that a wrapper function returns when using a decorator function in Python?

I am trying to access the time to execute a function that is inside a decorator function.
I followed the direction in this post because I was unable to get the function to take the arguments I passed to it. Now I am unsure of how to get the data from calling the function.
Here is my code:
import time
from functools import wraps
def sort_timer(func):
def outer(func):
#wraps(func)
def inner(*args, **kwargs):
start = time.perf_counter()
func(*args, **kwargs)
finish = time.perf_counter()
return start - finish
return inner
return outer
#sort_timer
def bubble_sort(a_list):
"""Sorts a_list in ascending order"""
for pass_num in range(len(a_list) - 1):
for index in range(len(a_list) - 1 - pass_num):
if a_list[index] > a_list[index + 1]:
temp = a_list[index]
a_list[index] = a_list[index + 1]
a_list[index + 1] = temp
list1 = [60, 19, 22, 14, 43, 27, 3, 77]
x = bubble_sort(list1)
print(x)
It would appear that what is being returned is the inner function. Here is what is logged to the console:
<function sort_timer.<locals>.outer.<locals>.inner at 0x0000027167770310>
Any insight would be appreciated. Thank you.
The code in the answer to which you refer is specifically for supplying arguments to the decorator. It will work as long as you put () after #sort_timer... and, as currently written, provide an argument. Here's an example, with the first func parameter renamed for clarity:
def sort_timer(parameter):
def outer(func):
#wraps(func)
def inner(*args, **kwargs):
start = time.perf_counter()
func(*args, **kwargs)
finish = time.perf_counter()
return start - finish
return inner
return outer
#sort_timer(13) # for example
...
That said, in your code as shown, you're not really using the double-wrapper technique to any advantage. Unless you plan on supplying arguments to the decorator itself (not the decorated function), you could simplify like so:
def sort_timer(func):
#wraps(func)
def inner(*args, **kwargs):
start = time.perf_counter()
func(*args, **kwargs)
finish = time.perf_counter()
return start - finish
return inner
Why not using a simple decorator for timing like e.g. mentioned in timeit versus timing decorator ?
import time
import functools
def timeit(f):
#functools.wraps(f)
def timed(*args, **kw):
ts = time.time()
result = f(*args, **kw)
te = time.time()
print(f"func:{f.__name__} args:{args}{'' if len(kw) == 0 else kw} took: {(te-ts)*1000 :.3f} msec")
return result
return timed
#timeit
def bubble_sort(a_list):
"""Sorts a_list in ascending order"""
for pass_num in range(len(a_list) - 1):
for index in range(len(a_list) - 1 - pass_num):
if a_list[index] > a_list[index + 1]:
temp = a_list[index]
a_list[index] = a_list[index + 1]
a_list[index + 1] = temp
list1 = [60, 19, 22, 14, 43, 27, 3, 77]
x = bubble_sort(list1)
which prints:
func:bubble_sort args:([3, 14, 19, 22, 27, 43, 60, 77],) took: 0.029 msec
The #functools.wraps(f) takes f's meta information and copies it to the decorating function timed, so that the resulting decorated function keeps the features of the original/target function.
Here is the code for calculating execute time of a function:
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
func(*args, **kwargs)
end = time.time() - start
print(end)
return wrapper

Whether the return value of a function modified by python decorator can only be Nonetype

I wrote a decorator that gets the runtime of the program, but the function return value becomes Nonetype.
def gettime(func):
def wrapper(*args, **kw):
t1 = time.time()
func(*args, **kw)
t2 = time.time()
t = (t2-t1)*1000
print("%s run time is: %.5f ms"%(func.__name__, t))
return wrapper
If I don't use the decorator, the return value is correct.
A = np.random.randint(0,100,size=(100, 100))
B = np.random.randint(0,100,size=(100, 100))
def contrast(a, b):
res = np.sum(np.equal(a, b))/(A.size)
return res
res = contrast(A, B)
print("The correct rate is: %f"%res)
The result is:The correct rate is: 0.012400
And if i use the decorator:
#gettime
def contrast(a, b):
res = np.sum(np.equal(a, b))/len(a)
return res
res = contrast(A, B)
print("The correct rate is: %f"%res)
There will report a error:
contrast run time is: 0.00000 ms
TypeError: must be real number, not NoneType
Of course, if I remove the print statement, I can get the correct running time, but the res accepts the Nonetype.
Since the wrapper replaces the function decorated, it also needs to pass on the return value:
def wrapper(*args, **kw):
t1 = time.time()
ret = func(*args, **kw) # save it here
t2 = time.time()
t = (t2-t1)*1000
print("%s run time is: %.5f ms"%(func.__name__, t))
return ret # return it here
Or you can do:
def gettime(func):
def wrapper(*args, **kw):
t1 = time.time()
func(*args, **kw)
t2 = time.time()
t = (t2-t1)*1000
print("%s run time is: %.5f ms"%(func.__name__, t))
print("The correct rate is: %f"%func(*args,**kw))
return wrapper
#gettime
def contrast(a, b):
res = np.sum(np.equal(a, b))/a.size
return res
contrast(A,B)

how a decorator works when the argument is recursive function?

import time
def clock(func):
def clocked(*args):
t0 = time.perf_counter()
result = func(*args)
elapsed = time.perf_counter() - t0
name = func.__name__
arg_str = ', '.join(repr(arg) for arg in args)
print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))
return result
return clocked
this is the decorator.
#clock
def factorial(n):
return 1 if n < 2 else n*factorial(n-1)
the parts of result is:
[0.00000191s] factorial(1) -> 1
[0.00004911s] factorial(2) -> 2
[0.00008488s] factorial(3) -> 6
[0.00013208s] factorial(4) -> 24
[0.00019193s] factorial(5) -> 120
[0.00026107s] factorial(6) -> 720
6! = 720
how this decorator works when the argument is recursive function? why the decorator can be executed for many times. how it works?
In your example, the clock decorator is executed once, when it replaces the original version of factorial with the clocked version. The original factorial is recursive and therefore the decorated version is recursive too. And so you get the timing data printed for each recursive call - the decorated factorial calls itself, not the original version, because the name factorial now refers to the decorated version.
It's a good idea to use functools.wraps in decorators. This copies various attributes of the original function to the decorated version.
For example, without wraps:
import time
def clock(func):
def clocked(*args):
''' Clocking decoration wrapper '''
t0 = time.perf_counter()
result = func(*args)
elapsed = time.perf_counter() - t0
name = func.__name__
arg_str = ', '.join(repr(arg) for arg in args)
print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))
return result
return clocked
#clock
def factorial(n):
''' Recursive factorial '''
return 1 if n < 2 else n * factorial(n-1)
print(factorial.__name__, factorial.__doc__)
output
clocked Clocking decoration wrapper
With wraps:
import time
from functools import wraps
def clock(func):
#wraps(func)
def clocked(*args):
''' Clocking decoration wrapper '''
t0 = time.perf_counter()
result = func(*args)
elapsed = time.perf_counter() - t0
name = func.__name__
arg_str = ', '.join(repr(arg) for arg in args)
print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))
return result
return clocked
#clock
def factorial(n):
''' Recursive factorial '''
return 1 if n < 2 else n * factorial(n-1)
print(factorial.__name__, factorial.__doc__)
output
factorial Recursive factorial
which is what we'd get if we did print(factorial.__name__, factorial.__doc__) on the undecorated version.
If you don't want the clock-decorated recursive function to print the timing info for all of the recursive calls, it gets a bit tricky.
The simplest way is to not use the decorator syntax and just call clock as a normal function so we get a new name for the clocked version of the function:
def factorial(n):
return 1 if n < 2 else n * factorial(n-1)
clocked_factorial = clock(factorial)
for n in range(7):
print('%d! = %d' % (n, clocked_factorial(n)))
output
[0.00000602s] factorial(0) -> 1
0! = 1
[0.00000302s] factorial(1) -> 1
1! = 1
[0.00000581s] factorial(2) -> 2
2! = 2
[0.00000539s] factorial(3) -> 6
3! = 6
[0.00000651s] factorial(4) -> 24
4! = 24
[0.00000742s] factorial(5) -> 120
5! = 120
[0.00000834s] factorial(6) -> 720
6! = 720
Another way is to wrap the recursive function in a non-recursive function and apply the decorator to the new function.
def factorial(n):
return 1 if n < 2 else n * factorial(n-1)
#clock
def nr_factorial(n):
return factorial(n)
for n in range(3, 7):
print('%d! = %d' % (n, nr_factorial(n)))
output
[0.00001018s] nr_factorial(3) -> 6
3! = 6
[0.00000799s] nr_factorial(4) -> 24
4! = 24
[0.00000801s] nr_factorial(5) -> 120
5! = 120
[0.00000916s] nr_factorial(6) -> 720
6! = 720
Another way is to modify the decorator so that it keeps track of whether it's executing the top level of the recursion or one of the inner levels, and only print the timing info for the top level. This version uses the nonlocal directive so it only works in Python 3, not Python 2.
def rclock(func):
top = True
#wraps(func)
def clocked(*args):
nonlocal top
if top:
top = False
t0 = time.perf_counter()
result = func(*args)
elapsed = time.perf_counter() - t0
name = func.__name__
arg_str = ', '.join(repr(arg) for arg in args)
print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))
else:
result = func(*args)
top = True
return result
return clocked
#rclock
def factorial(n):
return 1 if n < 2 else n * factorial(n-1)
for n in range(3, 7):
print('%d! = %d' % (n, factorial(n)))
output
[0.00001253s] factorial(3) -> 6
3! = 6
[0.00001205s] factorial(4) -> 24
4! = 24
[0.00001227s] factorial(5) -> 120
5! = 120
[0.00001422s] factorial(6) -> 720
6! = 720
The rclock function can be used on non-recursive functions, but it's a little more efficient to just use the original version of clock.
Another handy function in functools that you should know about if you're using recursive functions is lru_cache. This keeps a cache of recently computed results so they don't need to be re-computed. This can enormously speed up recursive functions. Please see the docs for details.
We can use lru_cache in conjunction with clock or rclock.
#lru_cache(None)
#clock
def factorial(n):
return 1 if n < 2 else n * factorial(n-1)
for n in range(3, 7):
print('%d! = %d' % (n, factorial(n)))
output
[0.00000306s] factorial(1) -> 1
[0.00017850s] factorial(2) -> 2
[0.00022049s] factorial(3) -> 6
3! = 6
[0.00000542s] factorial(4) -> 24
4! = 24
[0.00000417s] factorial(5) -> 120
5! = 120
[0.00000409s] factorial(6) -> 720
6! = 720
As you can see, even though we used the plain clock decorator only a single line of timing info gets printed for the factorials of 4, 5, and 6 because the smaller factorials are read from the cache instead of being re-computed.
Maybe it helps to assume the "syntactic sugar" point of view.
This is from PEP 318 with modifications (I simplified the example)
The current syntax for function decorators as implemented in Python 2.4a2 is:
#dec
def func(arg1, arg2, ...):
pass
This is equivalent to:
def func(arg1, arg2, ...):
pass
func = dec(func)
As you can see the decorator function is called only once and the wrapper it returns is assigned to the name of the decorated function.
Thus whenever the original function is called by its name (for example in a recursion) the wrapper (but not the decorator function) is called instead.
When you apply a decorator to a function, the function is passed as a parameter to the decorator. Whether the function is recursive or not does not matter.
The code
#clock
def factorial(n):
return 1 if n < 2 else n*factorial(n-1)
is equivalent to
def factorial(n):
return 1 if n < 2 else n*factorial(n-1)
factorial = clock(factorial)
The decorated function is passed to the decorator as an argument and returned another function to replace the original one, the returned function is no recursive function, but when you call it, it'll call the original recursive function:
def clock(func):
def clocked(*args):
t0 = time.perf_counter()
result = func(*args) # You call your original recursive function here
elapsed = time.perf_counter() - t0
name = func.__name__
arg_str = ', '.join(repr(arg) for arg in args)
print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))
return result
return clocked
When you call your decorated function factorial, what you actually called is clocked, and it actually call factorial in the following line:
result = func(*args)
The decorator is executed only once.
For understanding, you can think your function becomes to the following one after #clock:
def factorial(*args):
def _factorial(n):
return 1 if n < 2 else n*_factorial(n-1)
t0 = time.perf_counter()
result = _factorial(*args)
elapsed = time.perf_counter() - t0
name = _factorial.__name__
arg_str = ', '.join(repr(arg) for arg in args)
print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))
return result

Decorator makes functions return None

I built two functions to find prime factors. One version is slower than the other function on large number. I would like to evaluate the running time of those two functions. to do so I built a decorator to evaluate the time eclipsed.
Since I plug the decorator my two functions return None. What is wrong in my code?
import math
import time
def time_usage(func):
def wrapper(*args, **kwargs):
beg_ts = time.time()
func(*args, **kwargs)
end_ts = time.time()
print("[INFO] elapsed time: %f" % (end_ts - beg_ts))
return wrapper
#time_usage
def find_factors(n):
factors = []
i = 2
while i < n:
while (n % i) == 0:
factors.append(i)
n /= i
i += 1
if n > 1:
factors.append(n)
return factors
#time_usage
def improved_prime_factor(n):
factors = []
# No need to test whether the number is divisible by any
# even number other than 2
while n % 2 == 0:
factors.append(2)
n /= 2
i = 3
# If n = p * q, either p or q must be <= sqrt(n)
max_factor = math.sqrt(n)
while i <= max_factor:
while n % i == 0:
factors.append(i)
n /= i
# Update the upper band on possible factors
max_factor = math.sqrt(n)
i += 2
if n > 1:
factors.append(n)
return factors
if __name__ == '__main__':
print(improved_prime_factor(125556)) # return None
print(find_factors(125556)) # return None
You need to return func(*args, **kwargs). Also it is a good practice to decorate the wrapper method with functools.wraps
import functools
def time_usage(func):
#functools.wraps
def wrapper(*args, **kwargs):
beg_ts = time.time()
result = func(*args, **kwargs) # save the result to a name
end_ts = time.time()
print("[INFO] elapsed time: %f" % (end_ts - beg_ts))
return result # return the name
return wrapper

Difference between Python decorator with and without syntactic sugar?

I'm trying to implement a decorator that memoizes an arbitrary function. It appears I've successfully accomplished that with the following code:
def memoize(func):
cache = {}
def wrapper(*args, **kwargs):
acc = ""
for arg in args:
acc += str(arg)
if acc in cache:
return cache[acc]
else:
cache[acc] = func(*args, **kwargs)
return cache[acc]
return wrapper
#memoize
def fib(n):
if n == 0 or n == 1:
return 1
else:
return fib(n - 1) + fib(n - 2)
Then fib(100) returns 573147844013817084101 fairly quickly. However, if I don't use the syntactic sugar:
def fib(n):
if n == 0 or n == 1:
return 1
else:
return fib(n - 1) + fib(n - 2)
memoized = memoize(fib)
print memoized(100)
The function hangs. Debugging it, it looks like the wrapper returned is unable to modify the cache. Can someone explain this behavior? As far as I know there shouldn't be a difference between using the sugar and not using the sugar.
Your recursive call is not memoized, because you used a new name, not the original fib function name. Each fib() iteration calls back to fib(), but that will call the original, undecorated function.
Assign the return value of the decorator call to fib instead:
def fib(n):
if n == 0 or n == 1:
return 1
else:
return fib(n - 1) + fib(n - 2)
fib = memoize(fib)
print fib(100)
Alternatively, if you must use memoize as the name of the decorator result, have fib() call memoized() for recursive calls:
def fib(n):
if n == 0 or n == 1:
return 1
else:
return memoized(n - 1) + memoizzed(n - 2)
memoized = memoized(fib)
print memoized(100)
Remember, the #decorator syntax assigns to the same name, not a new name. The following two are equivalent:
#memoize
def fib(n):
# ....
and
def fib(n):
# ....
fib = memoize(fib) # Same name!
except the name fib is never bound to the original function first.

Categories

Resources