A brief analysis of Python writing function adorner

Source: Internet
Author: User
writing function adorners

This section focuses on writing the function decorator for the relevant content.

Trace Calls

The following code defines and applies a function adorner to count the number of calls to the decorated function, and prints the trace 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):p rint (A + B + c)

This is a decorator, written in class-decorated syntax, and tested as follows:

>>> spam (1 to spam6>>> spam (' A ', ' B ', ' C ') Call 2 to spamabc>>> spam.calls2>>& Gt Spam<__main__.tracer Object at 0x03098410>

At run time, the Tracer class and the decorated function are saved separately, and the subsequent calls to the decorated function are intercepted to add a logical layer to count and print each call.

After decorating, spam is actually an instance of the Tracer class.

The @ Adorner syntax avoids directly accidentally invoking the original function. Consider the non-adorner equivalent code as follows:

calls = 0def Tracer (Func,*args): global Callscalls + = 1print (' Call%s to%s '% (calls,func.__name__)) func (*args) def spam (A, B,C):p rint (a+b+c)

The test is as follows:

?12345>>> spam (1) 6>>> Tracer (spam,1,2,3) call to SPAM6

This workaround can be used on any function and does not require a special @ syntax, but unlike the adorner version, it requires additional syntax for every place in the code where the function is called. Although adorners are not required, they are usually the most convenient.

Extensions--Support for keyword parameters

The following code is an extended version of the preceding example, adding support for keyword parameters:

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):p rint (A + B + c) @tracerdef Egg (x, y):p rint (x**y)

The test is as follows:

>>> spam (2) call 1 to spam6>>> spam (a=4,b=5,c=6) call to Spam15>>> Egg (2,16) call 1 to Egg65 536>>> Egg (4,y=4) Call 2 to egg256

You can also see that the code here also uses "class instance properties" to hold the state, that is, the number of times the call is Self.calls. Both the wrapped function and the call counter are information for each instance.

Writing adorners with def function syntax

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

calls = 0def Tracer (func):d EF Wrapper (*args,**kargs): global Callscalls + = 1print (' Call%s to%s '% (calls,func.__name__)) return func (*args,**kargs) return wrapper@tracerdef spam (a,b,c):p rint (a+b+c) @tracerdef egg (x, y):p rint (x**y)

Here calls is defined as a global variable, which is cross-program, belongs to the entire module, not for each function, so that for any trace function call, the counter will increment, as follows test:

>>> spam (2) call 1 to spam6>>> spam (a=4,b=5,c=6) call to Spam15>>> Egg (2,16) call 3 to Egg65 536>>> Egg (4,y=4) call 4 to egg256

You can see that for the spam function and the egg function, the program uses the same counter.

So how to implement a counter for each function, we can use the new nonlocal statement in Python3, as follows:

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):p rint (a+b+c) @tracerdef egg (x, y):p rint (x**y) spam ( Spam (a=4,b=5,c=6) egg (2,16) egg (4,y=4)

Run as follows:

Call 1 to Spam6call 2 to Spam15call 1 to Egg65536call 2 to egg256

In this way, the calls variable is defined inside the tracer function so that it exists in a closed function scope, which is then modified by the nonlocal statement to modify the calls variable. This allows us to achieve the functionality we need.

Traps: Methods of decorating classes

Note that adorners written with classes cannot be used to decorate a function with the self parameter in a class, as described in the Python Adorner Foundation.
That is, if the adorner is written using 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 as follows in a method in a class:

Class Person:def __init__ (self,name,pay): Self.name = Nameself.pay = Pay@tracerdef giveraise (self,percent): Self.pay *= ( 1.0 + percent)

At this point the program will definitely go wrong. The root of the problem is that the self--of the __call__ method of the Tracer class is a tracer instance, and when we re-bind the adornment method name to a class instance object with __call__, Python passes the tracer instance to self, It does not pass the person body in the argument list at all. In addition, since Tracer does not know any information about the person instance that we are using to invoke the method invocation, there is no way to create a binding with one instance, so there is no way to properly allocate the call.

At this point we can only write adorners by means of nested functions.

Timed call

The following adorner will timing the invocation of a decorated function-both for the time of a call and for 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 ( Print (Result) print (' alltime =%s '%listcomp.alltime) print (') result = Mapcall (5) Mapcall (50000) mapcall (1000000) 500000) Mapcall (1000000) print (' alltime =%s '%mapcall.alltime) print (' Map/comp =%s '% round ( mapcall.alltime/listcomp.alltime,3))

The results of the operation are 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

It is important to note that the map operation returns an iterator in Python3, so its map operation does not correspond directly to the work of a list resolution, which in fact does not take time. So use list (map ()) to force it to build a list like List parsing

Add Adorner parameters

Sometimes we need an adorner to do an extra job, such as providing an output tag and opening or closing the trace message. This requires adorner parameters, and we can use adorner parameters to develop configuration options that can be encoded according to each decorated function. For example, add a label like this:

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

We can use such results in timers to allow a label and a tracking control flag to be passed in when decorating. For example, the following code:

Import timedef Timer (label= ", trace=true): Class Timer:def __init__ (self,func): Self.func = Funcself.alltime = 0def __cal L__ (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 timer function adorner can be used for any function, both in the module and in 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 (>>&) Gt x = Listcomp (>>> x = listcomp) >>> 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 () + listcomp:0 .00036,0.00036>>> x = Listcomp (listcomp:0.00034,0.00070>>> x = Listcomp () listcomp:0.00034,0.00104>>> listcomp.alltime0.0010432902706075842
 
  

About Python writing function decorator related knowledge small series to introduce to you here, I hope to help you!

  • Related Article

    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.