Let's start with a simple function that does something:
def sum(x,y):
print x+y;
return x+y;
Ok, but you probably want to remove the print before you let someone see your code... and maybe changes in the future will also need this debugging step. Our first 'decorator'
def debug(val):
print val;
return val;
def sum(x,y):
return x+y;
x,y=2,3;
# 'explicit decorator'
debug(sum(x,y))
Ok but we moved the print statement -out- of the function. Version controling between the master and debugging branch might still be weird. What we really want is a modified version of the function sum. What if a function could modify the call to sum:
def debug_on(func):
def debugging(*args,**kwargs):
print 'Scope: debugging',func(*args,**kwargs);
return func(*args,**kwargs);
print 'Scope: debug_on',debugging
return debugging
Read what is happening here carefully.
def debug_on(func):
def debugging(*args,**kwargs):
...
return debugging;
We take in a function, and return a function. What happens for example:
>> a = debug_on(sum); # a->debugging
# Note that debugging calls our original function sum
Scope: debug_on <function debugging at 0xf4b4d3ac>
We pass all positional (*args) and keyword (**kwargs) through debugging to sum and return the result.
>> print(a(3,4));
Scope: debugging, 7
7
In [30]:
from functools import wraps
def debug_on(func):
@wraps(func) #preserving the metadata for func
def debugging(*args,**kwargs):
retval = func(*args,**kwargs);
print('Scope: debugging %s:%s:%s'%(func,func.__name__,retval));
return retval;
print('Scope: debug_on',debugging)
return debugging
In [31]:
# the @ syntax is equivalent to function nesting https://www.python.org/dev/peps/pep-0318/
# debug_on(sum)
@debug_on
def sum(x,y): # sum->debugging
'''Return the sum'''
return x+y;
@debug_on
def sum2(x,y): #sum2->debugging, but seperate instance
'''Return the square of the sum'''
return (x+y)**2.;
print('Scope: main sum %s, sum2 %s'%(sum,sum2))
print('-'*10)
print('Q:What does %s do? A:%s'%(sum2.__name__,sum2.__doc__))
In [32]:
sum(3,4)
sum2(3,4)
Out[32]:
In [33]:
@debug_on
class point():
@debug_on
def __init__(self,x,y):
self.x=x;
self.y=y;
@debug_on
def dist(self):
return self.x**2.+self.y**2;
In [34]:
a = point(1,2);
a.dist()
Out[34]:
(Left for the reader)
def debug_on_classes(cls):
def debugging(*args,**kwargs):
cdict = cls.__dict__;
for item in cdict:
func = getattr(cls,item);
if( hasattr(func,'__call__') ):
setattr(cls,item,debug_on(func))
return cls(*args,**kwargs);
return debugging;
Note the careful syntax. Here we simply ask for all the items in the class dictionary and, if they are callable, we wrap then with the debug_on decorator. Test it out!
In [35]:
def debug_on_classes(cls):
@wraps(cls) #preserving the cls metadata
def debugging(*args,**kwargs):
cdict = cls.__dict__;
for item in cdict:
func = getattr(cls,item);
if( hasattr(func,'__call__') ):
setattr(cls,item,debug_on(func))
return cls(*args,**kwargs);
return debugging;
In [36]:
@debug_on_classes
class point():
def __init__(self,x,y):
self.x=x;
self.y=y;
def dist(self):
return self.x**2.+self.y**2;
a = point(1,2);
a.dist();
Decorators can take arguments. Basically this involves another layer on top of the decorator
from timeit import default_timer as timer
def time_execution(symb='*'*10):
def time_decorator(func):
def time_function(*args,**kwargs):
start = timer();
result=func(*args,**kwargs);
end = timer()-start;
print('Function call %s took %.2f seconds'%(func.__name__,end));
return result;
return time_function;
return time_decorator;
Note that this is just another function pointer on top of the time_decorator function. It manages the *args and **kwargs passed to the decorator
In [49]:
from timeit import default_timer as timer
from time import sleep;
def time_execution(*args,**kwargs):
symb = kwargs.pop('symb','*'*10)
def time_decorator(func):
def time_function(*args,**kwargs):
start = timer();
result=func(*args,**kwargs);
end = timer()-start;
print('%s Function call %s took %.2f seconds'%(symb,func.__name__,end));
return result;
return time_function;
return time_decorator;
@time_execution(symb='='*10) # note that @profile is provided by https://mg.pov.lt/profilehooks/
def recursive_counter(n=0):
while(n<10):
sleep(0.1);
n = recursive_counter(n+1);
return n;
print( recursive_counter(0) )