Analysis on function decorators written in Python

Source: Internet
Author: User
Tags python decorator
This article describes how to compile function decorators in Python. For more information, see Compile function decorators

This section describes how to compile a function decorator.

Trace call

The following code defines and applies a function decorator to count the number of calls to the decoration function, and prints tracking information for each call.

class tracer:def __init__(self,func):self.calls = 0self.func = funcdef __call__(self,*args):self.calls += 1print('call %s to %s' %(self.calls, self.func.__name__))self.func(*args)@tracerdef spam(a, b, c):print(a + b + c)

This is a modifier written by the class decoration syntax. The test is as follows:

>>> spam(1,2,3)call 1 to spam6>>> spam('a','b','c')call 2 to spamabc>>> spam.calls2>>> spam<__main__.tracer object at 0x03098410>

When running, the tracer class and the decoration function are saved separately, and the subsequent call to the decoration function is blocked, so that a logic layer can be added to count and print each call.

After decoration, spam is actually an instance of the tracer class.

@ Decorator syntax avoids accidental calls to the original function. Consider the equivalent code of the non-decorator as follows:

calls = 0def tracer(func,*args):global callscalls += 1print('call %s to %s'%(calls,func.__name__))func(*args)def spam(a,b,c):print(a+b+c)

The test is as follows:

?12345>>> spam(1,2,3)6>>> tracer(spam,1,2,3)call 1 to spam6

This alternative method can be used on any function without special @ syntax. However, unlike the decorator version, it requires additional syntax to call functions in code. Though decorator is not required, they are usually the most convenient.

Extension-supports keyword Parameters

The following code adds support for keyword parameters in the extended version of the previous example:

class tracer:def __init__(self,func):self.calls = 0self.func = funcdef __call__(self,*args,**kargs):self.calls += 1print('call %s to %s' %(self.calls, self.func.__name__))self.func(*args,**kargs)@tracerdef spam(a, b, c):print(a + b + c)@tracerdef egg(x,y):print(x**y)

The test is as follows:

>>> spam(1,2,3)call 1 to spam6>>> spam(a=4,b=5,c=6)call 2 to spam15>>> egg(2,16)call 1 to egg65536>>> egg(4,y=4)call 2 to egg256

You can also see that the code here uses the class instance attribute to save the status, that is, the number of calls self. call. The encapsulated functions and call counters are information about each instance.

Use def function syntax to write decorator

Using def to define the decorator function can also achieve the same effect. But there is a problem, we also need to close a counter in the scope, which changes with each call. We can naturally think of global variables as follows:

calls = 0def tracer(func):def wrapper(*args,**kargs):global callscalls += 1print('call %s to %s'%(calls,func.__name__))return func(*args,**kargs)return wrapper@tracerdef spam(a,b,c):print(a+b+c)@tracerdef egg(x,y):print(x**y)

Here, cballs is defined as a global variable. It is a cross-program and belongs to the entire module, rather than for each function. In this way, the counter will increase for any function call that is tracked, test as follows:

>>> spam(1,2,3)call 1 to spam6>>> spam(a=4,b=5,c=6)call 2 to spam15>>> egg(2,16)call 3 to egg65536>>> egg(4,y=4)call 4 to egg256

We can see that the program uses the same counter for the spam and egg functions.

Then, how can we implement the counter for each function? We can use the nonlocal statement added in Python3, as shown below:

def tracer(func):calls = 0def wrapper(*args,**kargs):nonlocal callscalls += 1print('call %s to %s'%(calls,func.__name__))return func(*args,**kargs)return wrapper@tracerdef spam(a,b,c):print(a+b+c)@tracerdef 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 command:

call 1 to spam6call 2 to spam15call 1 to egg65536call 2 to egg256

In this way, the CILS variable is defined inside the tracer function, so that it exists in a closed function scope. Then, the nonlocal statement is used to modify the scope and the CILS variable. In this way, we can implement the functions we need.

Trap: decoration method

[Note: decorator written in a class cannot be used to decorate functions with the self parameter in a class. This is described in the basics of the Python decorator]
That is, if the decorator is written in the following class:

class tracer:def __init__(self,func):self.calls = 0self.func = funcdef __call__(self,*args,**kargs):self.calls += 1print('call %s to %s'%(self.calls,self.func.__name__))return self.func(*args,**kargs)

When it is decorated with the following methods in the class:

class Person:def __init__(self,name,pay):self.name = nameself.pay = pay@tracerdef giveRaise(self,percent):self.pay *= (1.0 + percent)

At this time, the program will definitely fail. The root cause of the problem is that the tracer class's _ call _ method self is a tracer instance, when we use _ call _ to re-bind the decoration method name to a class instance object, Python only passes the tracer instance to self, it does not pass the Person body in the parameter list at all. In addition, because tracer does not know any information about the Person instance to be processed by using method calls, there is no way to create a binding method with an instance, so there is no way to allocate the Call correctly.

At this time, we can only compile the decorator through nested functions.

Timed call

The decorator below will time the call of a decoration function-either for the time of one call or the total time of all calls.

import timeclass timer:def __init__(self,func):self.func = funcself.alltime = 0def __call__(self,*args,**kargs):start = time.clock()result = self.func(*args,**kargs)elapsed = time.clock()- startself.alltime += elapsedprint('%s:%.5f,%.5f'%(self.func.__name__,elapsed,self.alltime))return result@timerdef listcomp(N):return [x*2 for x in range(N)]@timerdef 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'%listcomp.alltime)print('')result = mapcall(5)mapcall(50000)mapcall(500000)mapcall(1000000)print(result)print('allTime = %s'%mapcall.alltime)print('map/comp = %s '% round(mapcall.alltime/listcomp.alltime,3))

The running result is as follows:

listcomp:0.00001,0.00001listcomp:0.00885,0.00886listcomp:0.05935,0.06821listcomp:0.11445,0.18266[0, 2, 4, 6, 8]allTime = 0.18266365607537918mapcall:0.00002,0.00002mapcall:0.00689,0.00690mapcall:0.08348,0.09038mapcall:0.16906,0.25944[0, 2, 4, 6, 8]allTime = 0.2594409060462425map/comp = 1.42

Note that the map operation returns an iterator in Python3, so its map operation cannot directly correspond to the work of list parsing, that is, it does not take time. So we need to use list (map () to force it to build a list like list parsing.

Add decorator Parameters

Sometimes we need a decorator for extra work, such as providing an output tag and enabling or disabling tracing messages. In this case, decorator parameters are required. You can use the decorator parameters to set configuration options. These options can be encoded based on each decorative function. For example, add a tag as follows:

def timer(label = ''):def decorator(func):def onCall(*args):...print(label,...)return onCallreturn decorator@timer('==>')def listcomp(N):...

We can use this result in a timer to allow a tag and a trace control flag to be passed in during decoration. For example, the following code:

import timedef timer(label= '', trace=True):class Timer:def __init__(self,func):self.func = funcself.alltime = 0def __call__(self,*args,**kargs):start = time.clock()result = self.func(*args,**kargs)elapsed = time.clock() - startself.alltime += elapsedif trace:ft = '%s %s:%.5f,%.5f'values = (label,self.func.__name__,elapsed,self.alltime)print(format % value)return resultreturn Timer

This time function decorator can be used for any function, either in the module or in the interactive mode. We can test 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.
 
  .Timer object at 0x036DCC10>>>> listcomp.alltime0.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>>> listcomp.alltime0.0010432902706075842
 

I will introduce you to the Python function decorators and hope to help you!

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.