Writing function decorators
This section focuses on writing function decorators.
trace a call
The following code defines and applies a function decorator that counts the number of calls to the decorated function and prints trace information for each call.
class tracer: def __init__(self,func): = 0 = func def __call__(self,*args): += 1 print('call %s to %s' %(, .__name__)) (*args) @tracer def spam(a, b, c): print(a + b + c)
This is a decorator written through the syntax of class decoration and tested as follows:
>>> spam(1,2,3) call 1 to spam 6 >>> spam('a','b','c') call 2 to spam abc >>> 2 >>> spam <__main__.tracer object at 0x03098410>
At runtime, the tracer class is kept separate from the decorated function, and subsequent calls to the decorated function are intercepted in order to add a logical layer to count and print each call.
After decoration, spam is actually an instance of the tracer class.
The @decorator syntax avoids accidentally calling the initial function directly. Consider the non-decorator equivalent code shown below:
calls = 0 def tracer(func,*args): global calls calls += 1 print('call %s to %s'%(calls,func.__name__)) func(*args) def spam(a,b,c): print(a+b+c)
The test is as follows:
? 1 2 3 4 5 >>> spam(1,2,3) 6 >>> tracer(spam,1,2,3) call 1 to spam 6
This alternative can be used on any function and requires no special @ syntax, but unlike the decorator version, it requires additional syntax at each place in the code where the function is called. Although decorators are not required, they are usually the most convenient.
Extension - support for keyword parameters
The following code is an extended version of the previous example, adding support for keyword arguments:
class tracer: def __init__(self,func): = 0 = func def __call__(self,*args,**kargs): += 1 print('call %s to %s' %(, .__name__)) (*args,**kargs) @tracer def spam(a, b, c): print(a + b + c) @tracer def egg(x,y): print(x**y)
The test is as follows:
>>> spam(1,2,3) call 1 to spam 6 >>> spam(a=4,b=5,c=6) call 2 to spam 15 >>> egg(2,16) call 1 to egg 65536 >>> egg(4,y=4) call 2 to egg 256
It can also be seen that the code here also uses the [Class Instance Attribute] to save the state, i.e. the number of calls. Both the wrapped function and the call counter are specific to each instance of the information.
Writing decorators using def function syntax
The same effect can be achieved by defining a decorator function using def. But there is a catch, we also need a counter in the enclosing scope which changes with each call. We can naturally think of global variables as follows:
calls = 0 def tracer(func): def wrapper(*args,**kargs): global calls calls += 1 print('call %s to %s'%(calls,func.__name__)) return func(*args,**kargs) return wrapper @tracer def spam(a,b,c): print(a+b+c) @tracer def egg(x,y): print(x**y)
Here calls is defined as a global variable, which is cross-program and belongs to the whole module, not to each function, in which case the counter is incremented for any traced function call, as tested below:
>>> spam(1,2,3) call 1 to spam 6 >>> spam(a=4,b=5,c=6) call 2 to spam 15 >>> egg(2,16) call 3 to egg 65536 >>> egg(4,y=4) call 4 to egg 256
You can see that the program uses the same counter for both the spam function and the egg function.
So how to implement a counter for each function, we can use the new nonlocal statement in Python 3 as follows:
def tracer(func): calls = 0 def wrapper(*args,**kargs): nonlocal calls calls += 1 print('call %s to %s'%(calls,func.__name__)) return func(*args,**kargs) return wrapper @tracer def spam(a,b,c): print(a+b+c) @tracer def egg(x,y): print(x**y) spam(1,2,3) spam(a=4,b=5,c=6) egg(2,16) egg(4,y=4)
Run the following:
call 1 to spam 6 call 2 to spam 15 call 1 to egg 65536 call 2 to egg 256
In this way, the calls variable is defined inside the tracer function so that it exists in a closed function scope, and then the scope is modified by a nonlocal statement that modifies the calls variable. That way, we can achieve the functionality we need.
Trap: Decorative Class Methods
Note that decorators written using classes cannot be used to decorate functions of a class with a self parameter, as described in Python Decorator Basics].
That is, if the decorator is written as follows using classes:
class tracer: def __init__(self,func): = 0 = func def __call__(self,*args,**kargs): += 1 print('call %s to %s'%(,.__name__)) return (*args,**kargs)
when it decorates the following methods in the class:
class Person: def __init__(self,name,pay): = name = pay @tracer def giveRaise(self,percent): *= (1.0 + percent)
At this point the program will definitely go wrong. The root of the problem is that the __call__ method of the tracer class has a self - which is a tracer instance - and when we rebind the decorative method name to a class instance object with __call__, Python only passes the tracer instance to the self, it doesn't even did not pass the Person body in the argument list. In addition, since the tracer doesn't know anything about the Person instance we're dealing with with the method call, there's no way to create a method with a binding to an instance, and so there's no way to assign the call correctly.
At this point we can only write decorators by nesting functions.
time call
The following decorator will time calls to a decorated function - both for a single call and in total for all calls.
import time class timer: def __init__(self,func): = func = 0 def __call__(self,*args,**kargs): start = () result = (*args,**kargs) elapsed = ()- start += elapsed print('%s:%.5f,%.5f'%(.__name__,elapsed,)) return result @timer def listcomp(N): return [x*2 for x in range(N)] @timer def mapcall(N): return list(map((lambda x :x*2),range(N))) result = listcomp(5) listcomp(50000) listcomp(500000) listcomp(1000000) print(result) print('allTime = %s'%) print('') result = mapcall(5) mapcall(50000) mapcall(500000) mapcall(1000000) print(result) print('allTime = %s'%) print('map/comp = %s '% round(/,3))
The results of the run are as follows:
listcomp:0.00001,0.00001 listcomp:0.00885,0.00886 listcomp:0.05935,0.06821 listcomp:0.11445,0.18266 [0, 2, 4, 6, 8] allTime = 0.18266365607537918 mapcall:0.00002,0.00002 mapcall:0.00689,0.00690 mapcall:0.08348,0.09038 mapcall:0.16906,0.25944 [0, 2, 4, 6, 8] allTime = 0.2594409060462425 map/comp = 1.42
The important thing to note here is that the map operation returns an iterator in Python 3, so its map operation can't correspond directly to a list parsing job, i.e. it doesn't actually take time. So use list(map()) to force it to build a list like list parsing does
Adding Decorator Parameters
Sometimes we need a decorator to do an extra job, such as providing an output label and the ability to turn tracking messages on or off. This requires the use of decorator parameters, which we can use to make configuration options that can be coded for each decorated function. For example, add labels like the following:
def timer(label = ''): def decorator(func): def onCall(*args): ... print(label,...) return onCall return decorator @timer('==>') def listcomp(N):...
We can use such a result in a timer to allow a label and a tracking control flag to be passed in when decorating. For example, the following code:
import time def timer(label= '', trace=True): class Timer: def __init__(self,func): = func = 0 def __call__(self,*args,**kargs): start = () result = (*args,**kargs) elapsed = () - start += elapsed if trace: ft = '%s %s:%.5f,%.5f' values = (label,.__name__,elapsed,) print(format % value) return result return Timer
This timed function decorator can be used for any function, both in the module and in interactive mode. We can test it in interactive mode as follows:
>>> @timer(trace = False) def listcomp(N): return [x * 2 for x in range(N)] >>> x = listcomp(5000) >>> x = listcomp(5000) >>> x = listcomp(5000) >>> listcomp <__main__.timer.<locals>.Timer object at 0x036DCC10> >>> 0.0011475424533080223 >>> >>> @timer(trace=True,label='\t=>') def listcomp(N): return [x * 2 for x in range(N)] >>> x = listcomp(5000) => listcomp:0.00036,0.00036 >>> x = listcomp(5000) => listcomp:0.00034,0.00070 >>> x = listcomp(5000) => listcomp:0.00034,0.00104 >>> 0.0010432902706075842</locals>
About Python write function decorator related knowledge I will introduce to you here, I hope to help you!