Multiple decorators in Python

  • 2020-05-07 19:58:15
  • OfStack

Multiple decorators, that is, multiple decorators decorate the same object.

1. Decorator without parameters:


>>> def first(func):
    print '%s() was post to first()'%func.func_name
    def _first(*args,**kw):
        print 'Call the function %s() in _first().'%func.func_name
        return func(*args,**kw)
    return _first
>>> def second(func):
    print '%s() was post to second()'%func.func_name
    def _second(*args,**kw):
        print 'Call the function %s() in _second().'%func.func_name
        return func(*args,**kw)
    return _second
>>> @first
@second
def test():return 'hello world' test() was post to second()
_second() was post to first()
>>> test()
Call the function _second() in _first().
Call the function test() in _second().
'hello world'
>>>

It is actually equivalent to the following code:


>>> def test():
    return 'hello world' >>> test=second(test)
test() was post to second()
>>> test
<function _second at 0x000000000316D3C8>
>>> test=first(test)
_second() was post to first()
>>> test
<function _first at 0x000000000316D358>
>>> test()
Call the function _second() in _first().
Call the function test() in _second().
'hello world'
>>>

2. The decorator has parameters:

>>> def first(printResult=False):
    def _first(func):
        print '%s() was post to _first()'%func.func_name
        def __first(*args,**kw):
            print 'Call the function %s() in __first().'%\
                  func.func_name
            if printResult:
                print func(*args,**kw),'#print in __first().'
            else:
                return func(*args,**kw)
        return __first
    return _first >>> def second(printResult=False):
    def _second(func):
        print '%s() was post to _second()'%func.func_name
        def __second(*args,**kw):
            print 'Call the function %s() in __second().'%\
                  func.func_name
            if printResult:
                print func(*args,**kw),'#print in __second().'
            else:
                return func(*args,**kw)
        return __second
    return _second >>> @first(True)
@second(True)
def test():
    return 'hello world' test() was post to _second()
__second() was post to _first()
>>> test()
Call the function __second() in __first().
Call the function test() in __second().
hello world #print in __second().
None #print in __first().
>>>

As above, line 35 outputs and then calls s second (), which then calls s test() and s en test(), and then returns to s print () to continue with s en, and this s print statement s en is s None returned by s second ()

It is equivalent to:


>>> def test():
    return 'hello world' >>> test=second(True)(test)
test() was post to _second()
>>>
>>> test
<function __second at 0x000000000316D2E8>
>>> test=first(True)(test)
__second() was post to _first()
>>> test
<function __first at 0x0000000003344C18>
>>>

3. Application of multiple decorators:

For example, if you are a project manager, you require that every block of code must have an ArgsType parameter check and an ResponsibilityRegister responsibility check, which requires two decorators to monitor this block of code.


#coding=utf-8
import os,sys,re
from collections import OrderedDict def ArgsType(*argTypes,**kwTypes):
    u'''ArgsType(*argTypes,**kwTypes)
    options=[('opt_UseTypeOfDefaultValue',False)]     The following is the function related to the switch, not the type verification related to the keyword parameters, all options:
    opt_UseTypeOfDefaultValue=>bool:False, for True When there is no specified type
                               An argument to a value USES the type of its default value
    '''
    def _ArgsType(func):
        # Identify all parameter name
        argNames=func.func_code.co_varnames[:func.func_code.co_argcount]
        # Identify all default parameter
        defaults=func.func_defaults
        if defaults:
            defaults=dict(zip(argNames[-len(defaults):],defaults))
        else:defaults=None
        # Set all" options Keyword parameters are presented
        options=dict()
        for option,default in [('opt_UseTypeOfDefaultValue',False)]:
            options[option]=kwTypes.pop(option,default)
        #argTypes and kwTypes The total length should be argNames1 to
        if len(argTypes)+len(kwTypes)>len(argNames):
            raise Exception('Too much types to check %s().'%func.func_name)
        # all kwTypes The key in cannot be overwritten in argTypes Is already occupied names
        if not set(argNames[len(argTypes):]).issuperset(
            set(kwTypes.keys())):
            raise Exception('There is some key in kwTypes '+
                'which is not in argNames.')
        # Make sure all the parameters should be there types
        types=OrderedDict()
        for name in argNames:types[name]=None
        if len(argTypes):
            for i in range(len(argTypes)):
                name=argNames[i]
                types[name]=argTypes[i]
        else:
            for name,t in kwTypes.items():
                types[name]=t
        if len(kwTypes):
            for name,t in kwTypes.items():
                types[name]=t
        # about default parameter the type
        if options['opt_UseTypeOfDefaultValue']:
            for k,v in defaults.items():
                # if default parameter the type If not specified otherwise, use
                #default parameter the default value the type
                if types[k]==None:
                    types[k]=type(v)
        def __ArgsType(*args,**kw):
            #order the args
            Args=OrderedDict()
            #init keys
            for name in argNames:Args[name]=None
            #init default values
            if defaults is not None:
                for k,v in defaults.items():
                    Args[k]=v
            #fill in all args
            for i in range(len(args)):
                Args[argNames[i]]=args[i]
            #fill in all keyword args
            for k,v in kw.items():
                Args[k]=v
            #check if there is some None in the values
            if defaults==None:
                for k in Args:
                    if Args[k]==None:
                        if defaults==None:
                            raise Exception(('%s() needs %r parameter, '+
                                'which was not given')%(func.func_name,k))
                        else:
                           if not defaults.has_key(k):
                                raise Exception(('Parameter %r of %s() is'+
                                    ' not a default parameter')%\
                                    (k,func.func_name))
            #check all types
            for k in Args:
                if not isinstance(Args[k],types[k]):
                    raise TypeError(('Parameter %r of %s() must be '+
                        'a %r object, but you post: %r')%\
                        (k,func.func_name,types[k],Args[k]))
            return func(*args,**kw)
        return __ArgsType
    return _ArgsType def ResponsibilityRegister(author):
    def _ResponsibilityRegister(func):
        def __ResponsibilityRegister(*args,**kw):
            try:
                return func(*args,**kw)
            except Exception as e:
                print ("Something is wrong, It's %s's responsibility."%\
                       author).center(80,'*')
                raise e
        return __ResponsibilityRegister
    return _ResponsibilityRegister @ResponsibilityRegister('Kate')
@ArgsType(str,int)
def left(Str,Len=1):
    return Str[:Len] print 'Good calling:'
print left('hello world',8)
print 'Bad calling:'
print left(3,7)

There is no documentation, so the caller does not know that the wrong call was used, causing the error, which is the responsibility of Kate.

Multiple decorators can be used when there are two separate checks on the code, as shown above.


Related articles: