Note:This is a StackOverflow question answer, and because this answer is so good, I archived it


Q: How do I use multiple function decorators in Python in succession?


If you don’t want to see a detailed explanation, you can read Paolo Bergantino’s answer

Decorator foundation

Python’s decorators are all objects

To understand decorators, you must first know that functions in Python are object objects. This is very important. Let’s look at an example to see why.

def shout(word='yes'): return word.capitalize() + '! ' print shout() # outputs : 'Yes! '# as an object, you can assign a function to a variable, just like any other object So instead of calling the function, we just assign the function 'shout' to the variable 'scream' # which means we can call the function 'shout' from 'scream' print scream() # outputs: 'Yes! 'in addition to this, it also means that you can remove the old function name' shout ', which is still accessible via 'scream' del shout try: Print shout() except NameError as e: print e #outputs: "name 'shout' is not defined" print scream() # outputs: 'Yes! 'Copy the code

Remember that, and we’ll use it again.

Another interesting property of Python functions is that they can… Define it inside another function!

Def whisper(word='yes'): return word.lower() + '... '#... Print whisper() # You can call the 'talk' function, which defines the 'whisper' function each time. Outputs: < outputs > < outputs > < outputs > < outputs > < outputs > < outputs > Elsif elsif elsif elsif elsif elsif elsif elsif elsif elsif elsif elsif elsif "name 'whisper' is not defined"* #Python's functions are objectsCopy the code

Function reference

Now for the fun part…

You already know that functions are object objects. Furthermore, the function also:

  • You can assign values like variables
  • It can be defined inside another function

This means that a function can return another function. Check it out! ☺

Def shout(word='yes'): return word.capitalize() + 'capitalize '! ' def whisper(word='yes'): return word.lower() + '... '# then we return one of the above two functions if kind == 'shout': # we don't use '()'. So we're not calling a function; # Instead, we return the function object Return shout else: Return Whisper # How do you use this weird feature? Outputs this function, then assign the result to a variable talk = getTalk() <function shout at 0xb7ea817c> # The object is the one returned by the function: Outputs: Yes; outputs: Yes; outputs: Yes Print getTalk('whisper')() #outputs: yes...Copy the code

But wait… And something else!

If you can return a function, you can also pass the function as an argument:

def doSomethingBefore(func): 
    print 'I do something before then I call the function you gave me'
    print func()
 
doSomethingBefore(scream)
#outputs: 
#I do something before then I call the function you gave me  
#Yes!Copy the code

Ok, you’ve got everything you need to know about decorators. As you can see, decorators are “wrappers,” that is, they allow you to run other code in front and behind the functions they decorate without having to modify the functions themselves.

Make your own decorators

How do you do it:

Def my_shiny_new_decorator(a_function_to_decorate): # Inside the decorator, the decorator temporarily creates a function: wrapper. This function wraps the original function so that it can execute other code before and after the original function. def the_wrapper_around_the_original_function(): Print 'Before the function runs' # Call the original function here (using parentheses) a_function_to_decorate() # Write the code you want to execute after the original function is called here Print 'After the function runs' # so far, 'a_function_to_decorate' has never been executed. The wrapper contains the original function and the code executed before and after the original function. It's available now! Return the_wrapper_around_the_original_function # Now imagine that you created a function and you don't want to change it anymore. def a_stand_alone_function(): Outputs: print 'I am a stand alone function, don't you dare modify me' a_stand_alone_function() #outputs: I am a stand alone function, don't you dare modify me # Ok, you can decorate this function to extend its functionality # Just pass it to the decorator, and then it will dynamically wrap in whatever code you need, Then return a new function that satisfies your needs:  a_stand_alone_function_decorated = my_shiny_new_decorator(a_stand_alone_function) a_stand_alone_function_decorated() #outputs: #Before the function runs #I am a stand alone function, don't you dare modify me #After the function runsCopy the code

Now, you want every time you call a_stand_alone_function, actually A_stand_alone_function_decorated will be called. That is, this simply overwrites the a_stand_alone_function function with the function returned by my_shiny_new_decorator:

a_stand_alone_function = my_shiny_new_decorator(a_stand_alone_function) a_stand_alone_function() #outputs: #Before the function runs #I am a stand alone function, don't you dare modify me #After the function runs # guess what? That's actually how decorators work!Copy the code

Decorator decryption

Same example as before, but with decorator syntax:

@my_shiny_new_decorator
def another_stand_alone_function():
    print 'Leave me alone'
 
another_stand_alone_function()  
#outputs:  
#Before the function runs
#Leave me alone
#After the function runsCopy the code

That’s it. That’s how simple decorators are. @decorator is just shorthand for the following form:

another_stand_alone_function = my_shiny_new_decorator(another_stand_alone_function)Copy the code

Decorator is just a variation of pythonic’s decorator design pattern. There are many traditional design patterns built into Python to simplify the development process (such as iterators).

Of course, you can overlay multiple decorators:

def bread(func):
    def wrapper():
        print "</''''''\>"
        func()
        print "<\______/>"
    return wrapper
 
def ingredients(func):
    def wrapper():
        print '#tomatoes#'
        func()
        print '~salad~'
    return wrapper
 
def sandwich(food='--ham--'):
    print food
 
sandwich()
#outputs: --ham--
sandwich = bread(ingredients(sandwich))
sandwich()
#outputs:
#</''''''\>
# #tomatoes#
# --ham--
# ~salad~
#<\______/>Copy the code

Using Python’s decorator syntax:

<a href="http://www.jobbole.com/members/bread">@bread</a>
@ingredients
def sandwich(food='--ham--'):
    print food
 
sandwich()
#outputs:
#</''''''\>
# #tomatoes#
# --ham--
# ~salad~
#<\______/>Copy the code

The order in which you set your decorators is important:

@ingredients
<a href="http://www.jobbole.com/members/bread">@bread</a>
def strange_sandwich(food='--ham--'):
    print food
 
strange_sandwich()
#outputs:
##tomatoes#
#</''''''\>
# --ham--
#<\______/>
# ~salad~Copy the code


Now: Time to answer the question…

Now you can easily answer this question:

Def makebold(fn): def wrapper(): Return '<b>' + fn() + '</b>' return wrapper # Generate italic decorator def makeitalic(fn): Return '< I >' + fn() + '</ I >' return wrapper @makebold @makeitalic def say(): Outputs: < p > outputs: < p > outputs: < p > outputs: < p > outputs: < p > outputs: < p > return 'hello' say = makebold(makeitalic(say)) print say() #outputs: <b><i>hello</i></b>Copy the code

It’s time to take a look at some of the more advanced ways to use decorators.


Pass the decorator to the next level

Pass arguments to the decorated function

Def a_decorator_passing_arguments(function_to_decorate): def a_wrapper_accepting_arguments(arg1, arg2): print 'I got args! Look:', arg1, arg2 function_to_decorate(arg1, arg2) return a_wrapper_accepting_arguments # Because when you call the function returned by the decorator, you're actually calling the wrapper, Pass the argument to the decorator function @a_decorator_passing_arguments def print_full_name(first_name, last_name): print 'My name is', first_name, last_name print_full_name('Peter', 'Venkman') # outputs: #I got args! Look: Peter Venkman #My name is Peter VenkmanCopy the code

Decorator method

One of the nice things about Python is that methods and functions are essentially the same. The only difference is that the first argument to the method is a reference to the current object (self).

This means you can create decorators for methods in the same way! Just remember to consider self:

def method_friendly_decorator(method_to_decorate): def wrapper(self, lie): lie = lie - 3 # very friendly, decrease age even more :-) return method_to_decorate(self, lie) return wrapper class Lucy(object): def __init__(self): self.age = 32 @method_friendly_decorator def sayYourAge(self, lie): print 'I am {0}, what did you think? '.format(self.age + lie) l = Lucy() l.sayYourAge(-3) #outputs: I am 26, what did you think?Copy the code

If you’re creating a generic decorator — a decorator that applies to any function or method, no matter what the arguments are — then just use *args, **kwargs:

def a_decorator_passing_arbitrary_arguments(function_to_decorate): Def a_wrapper_accepting_arbitrary_arguments(*args, **kwargs): print 'Do I have args? :' print args print kwargs' :' print args print kwargs'  # http://www.saltycrane.com/blog/2008/01/how-to-use-args-and-kwargs-in-python/ function_to_decorate(*args, **kwargs) return a_wrapper_accepting_arbitrary_arguments @a_decorator_passing_arbitrary_arguments def function_with_no_argument(): print 'Python is cool, no argument here.' function_with_no_argument() #outputs #Do I have args?: #() #{} #Python is cool, no argument here. @a_decorator_passing_arbitrary_arguments def function_with_arguments(a, b, C): print a, b, c function_with_arguments(1,2,3) #outputs #Do I have args? #(1, 2, 3) #{} #1 2 3 @a_decorator_passing_arbitrary_arguments def function_with_named_arguments(a, b, c, platypus='Why not ?'): print 'Do {0}, {1} and {2} like platypus? {3}'.format( a, b, c, platypus) function_with_named_arguments('Bill', 'Linus', 'Steve', platypus='Indeed! ') #outputs #Do I have args ? : #('Bill', 'Linus', 'Steve') #{'platypus': 'Indeed! '} #Do Bill, Linus and Steve like platypus? Indeed! class Mary(object): def __init__(self): self.age = 31 @a_decorator_passing_arbitrary_arguments def sayYourAge(self, lie=-3): Print 'I am {0}, what did you think? '.format(self.age + lie) m = Mary() m.sayYourAge() #outputs # Do I have args? : #(<__main__.Mary object at 0xb7d303ac>,) #{} #I am 28, what did you think?Copy the code

Pass the parameters to the decorator

Great, now what do you think about passing parameters to the decorator itself?

This may be a bit strange, since the decorator must accept a function as an argument. Therefore, you may not be able to pass a decorator function as an argument directly to another decorator.

Before we get the answer, let’s write a small example:

Def my_decorator(func): print 'I am an ordinary function' def wrapper(): Def lazy_function(): print 'I am function returned by the decorator' func() return wrapper # print 'zzzzzzzz' decorated_function = my_decorator(lazy_function) #outputs: I am an ordinary function # The function above prints 'I am an ordinary function', because this is actually the result of calling the function directly. Nothing surprising. @my_decorator def lazy_function(): print 'zzzzzzzz' #outputs: I am an ordinary functionCopy the code

The result is exactly the same: my_decorator is invoked. So when you use @my_decorator, Python calls the function represented by the “my_decorator” variable.

This is important! The variable you provide may or may not point to the decorator.

Let’s make it harder. ☺

def decorator_maker(): print 'I make decorators! I am executed only once: '+\ 'when you make me create a decorator.' def my_decorator(func): print 'I am a decorator! I am executed only when you decorate a function.' def wrapped(): print ('I am the wrapper around the decorated function. ' 'I am called when you call the decorated function. ' 'As the wrapper, I return the RESULT of the decorated function.') return func() print 'As the decorator, I return the wrapped function.' return wrapped print 'As a decorator maker, I return a decorator' return my_decorator # Lets create a decorator. Outputs: < outputs > new_decorator = decorator_maker() #outputs: #I make decorators! I am executed only once: when you make me create a decorator. #As a decorator maker, Def decorated_function(): print 'I am the decorated function.' decorated_function = new_decorator(decorated_function) #outputs: #I am a decorator! I am executed only when you decorate a function. #As the decorator, State_state_output () {return the wrapped function #I am the wrapper around the decorated function. I am called when you call the decorated function. #As the wrapper, I return the RESULT of the decorated function. #I am the decorated function.Copy the code

Nothing unexpected happened.

Let’s do the same thing again, but this time cancel all intermediate variables:

def decorated_function(): print 'I am the decorated function.' decorated_function = decorator_maker()(decorated_function) #outputs: #I make decorators! I am executed only once: when you make me create a decorator. #As a decorator maker, I return a decorator #I am a decorator! I am executed only when you decorate a function. #As the decorator, I return the wrapped function. # finally:  decorated_function() #outputs: #I am the wrapper around the decorated function. I am called when you call the decorated function. #As the wrapper, I return the RESULT of the decorated function. #I am the decorated function.Copy the code

Make it shorter:

@decorator_maker() def decorated_function(): print 'I am the decorated function.' #outputs: #I make decorators! I am executed only once: when you make me create a decorator. #As a decorator maker, I return a decorator #I am a decorator! I am executed only when you decorate a function. #As the decorator, I return the wrapped function. # finally:  decorated_function() #outputs: #I am the wrapper around the decorated function. I am called when you call the decorated function. #As the wrapper, I return the RESULT of the decorated function. #I am the decorated function.Copy the code

Did you notice that? We called a function with @ syntax! : -)

So, back to the decorator parameters. If we can use a function to generate a temporary decorator, we can also pass arguments to that function, right?

def decorator_maker_with_arguments(decorator_arg1, decorator_arg2): print 'I make decorators! And I accept arguments:', decorator_arg1, decorator_arg2 def my_decorator(func): # The ability to pass parameters comes from closures # If you don't know closures, that's ok, # or you can also read http://stackoverflow.com/questions/13857/can-you-explain-closures-as-they-relate-to-python print 'I am the Somehow you passed me arguments:', decorator_arg1, decorator_arg2 # def wrapped(function_arg1, function_arg2): print ('I am the wrapper around the decorated function.\n' 'I can access all the variables\n' '\t- from the decorator: {0} {1}\n' '\t- from the function call: {2} {3}\n' 'Then I can pass them to the decorated function' .format(decorator_arg1, decorator_arg2, function_arg1, function_arg2)) return func(function_arg1, function_arg2) return wrapped return my_decorator @decorator_maker_with_arguments('Leonard', 'Sheldon') def decorated_function_with_arguments(function_arg1, function_arg2): print ('I am the decorated function and only knows about my arguments: {0}' ' {1}'.format(function_arg1, function_arg2)) decorated_function_with_arguments('Rajesh', 'Howard') #outputs: #I make decorators! And I accept arguments: Leonard Sheldon #I am the decorator. Somehow you passed me arguments: Leonard Sheldon #I am the wrapper around the decorated function. #I can access all the variables # - from the decorator:  Leonard Sheldon # - from the function call: Rajesh Howard #Then I can pass them to the decorated function #I am the decorated function and only knows about my arguments: Rajesh HowardCopy the code

The result: a decorator with parameters. Parameters can be set to variables:

c1 = 'Penny'
c2 = 'Leslie'
 
@decorator_maker_with_arguments('Leonard', c1)
def decorated_function_with_arguments(function_arg1, function_arg2):
    print ('I am the decorated function and only knows about my arguments:'
           ' {0} {1}'.format(function_arg1, function_arg2))
 
decorated_function_with_arguments(c2, 'Howard')
#outputs:
#I make decorators! And I accept arguments: Leonard Penny
#I am the decorator. Somehow you passed me arguments: Leonard Penny
#I am the wrapper around the decorated function. 
#I can access all the variables 
#    - from the decorator: Leonard Penny 
#    - from the function call: Leslie Howard 
#Then I can pass them to the decorated function
#I am the decorated function and only knows about my arguments: Leslie HowardCopy the code

As you can see, you can use this technique to pass arguments to the decorator just as you would to a normal function. You can even use *args, **kwargs if you wish. But remember, decorators are only called once. Run only when Python imports the script. You can’t set parameters dynamically after that. When you execute import X, the function is already decorated, so you can’t change anything afterwards.


Exercise: Decorate a decorator

Well, as a bonus, I’ll give you a piece of code that allows the decorator to accept any parameters. After all, to receive arguments, we create a decorator with another function.

Let’s wrap up the decorator.

What other wrapper functions have we seen recently?

Yes, decorators!

Let’s do something fun and write a decorator for decorator:

Def decorator_with_args(decorator_to_enhance): """ This function is used as a decorator. It decorates other functions, and the decorated function is also a decorator. Have a cup of coffee. Def decorator_maker(*args, **kwargs) def decorator_maker(*args, **kwargs): We create a temporary decorator that accepts only one function but cannot pass def decorator_wrapper(func) from maker: the result returned by the original decorator is really just a normal function (this function returns a function). The only catch is that decorators must have a specific format or they won't work:  return decorator_to_enhance(func, *args, **kwargs) return decorator_wrapper return decorator_makerCopy the code

It can be used like this:

Create a function to use as a decorator. Then add a decorator :-) # Don't forget, Def decorated_decorator(func, *args, **kwargs) @decorator_with_args def decorated_decorator(func, *args, **kwargs): def wrapper(function_arg1, function_arg2): Print 'Decorated with', args, kwargs return func(function_arg1, function_arg2) return Wrapper # Then decorate your functions with brand new decorators. @decorated_decorator(42, 404, 1024) def decorated_function(function_arg1, function_arg2): print 'Hello', function_arg1, function_arg2 decorated_function('Universe and', 'everything') #outputs: #Decorated with (42, 404, 1024) {} #Hello Universe and everything # Whoooot!Copy the code

I know the last time you felt this way was when you heard someone say, “You have to understand recursion before you can understand recursion.” But now, don’t you think it’s great to have mastered this?


Best practice: Decorators

  • Decorators were introduced in Python 2.4, so make sure your code is running Python version >=2.4
  • Decorators slow down function calls. Please keep in mind
  • You cannot undecorate a function.(indeedThere areThere are some tricks to creating decorators that allow de-decorators, but no one will use them. So once the function is decorated,All the code for this functionIt’s all decorated.
  • Decorators wrap functions, making them harder to debug. Improved from Python >=2.5; See below.)

The functools module was introduced in Python 2.5. The module contains the function functools.wraps(), which copies the name of the decorated function, the module name, and docString into its wrapper.

(Funny thing: Functools. wraps() is a wraps! ☺)

__name__ def foo(): print 'foo' print foo.__name__ #outputs: foo # def wrapper(): print 'bar' return func() return wrapper @bar def foo(): print 'foo' print foo.__name__ #outputs: Wrapper # 'functools' import functools def bar(func): # we think 'wrapper' is wrapping 'func' # something magical happened @functools.wraps(func) def wrapper(): print 'bar' return func() return wrapper @bar def foo(): print 'foo' print foo.__name__ #outputs: fooCopy the code


How can decorators be useful?

Now the big question is: What can I do with decorators?

Decorators look cool and powerful, but it’s nice to have a working example. There are about 1,000 possible examples. Common uses are to extend the behavior of an external library function (which you can’t modify), or to debug an external library function (which you don’t want to modify because it’s temporary).

You can use decorators to extend functions in a DRY(Don’t Repeat Yourself) manner, like this:

Def benchmark(func): """ import time def wrapper(*args, **kwargs): t = time.clock() res = func(*args, **kwargs) print func.__name__, Time.clock ()-t return res return Wrapper def logging(func): """ A decorator used to record script activity. (It's actually just printed, but it can be printed to the log!) """ def wrapper(*args, **kwargs): res = func(*args, **kwargs) print func.__name__, args, kwargs return res return wrapper def counter(func): """ def wrapper(*args, **kwargs): wrapper.count = wrapper.count + 1 res = func(*args, **kwargs) print '{0} has been used: {1}x'.format(func.__name__, wrapper.count) return res wrapper.count = 0 return wrapper @counter @benchmark @logging def reverse_string(string): return str(reversed(string)) print reverse_string('Able was I ere I saw Elba') print reverse_string('A man, a plan, a canoe, pasta, heros, rajahs, a coloratura, maps, snipe, percale, macaroni, a gag, a banana bag, a tan, a tag, a banana bag again (or a camel), a crepe, pins, Spam, a rut, a Rolo, cash, a jar, sore hats, a peon, a canal: Panama! ') #outputs: #reverse_string ('Able was I ere I saw Elba',) {} #wrapper 0.0 #wrapper has been used: 1x #ablE was I ere I saw elbA #reverse_string ('A man, a plan, a canoe, pasta, heros, rajahs, a coloratura, maps, snipe, percale, macaroni, a gag, a banana bag, a tan, a tag, a banana bag again (or a camel), a crepe, pins, Spam, a rut, a Rolo, cash, a jar, sore hats, a peon, a canal: Panama! ',) {} #wrapper 0.0 #wrapper has been used: 2x #! amanaP :lanac a ,noep a ,stah eros ,raj a ,hsac ,oloR a ,tur a ,mapS ,snip ,eperc a ,)lemac a ro( niaga gab ananab a ,gat a ,nat a ,gab ananab a ,gag a ,inoracam ,elacrep ,epins ,spam ,arutaroloc a ,shajar ,soreh ,atsap ,eonac a ,nalp a ,nam ACopy the code

Of course, the advantage of decorators is that you can use them on almost any function without having to rewrite the function. DRY (Don’t Repeat Yourself), as I said:

@counter @benchmark @logging def get_random_futurama_quote(): from urllib import urlopen result = urlopen('http://subfusion.net/cgi-bin/quote.pl?quote=futurama').read() try: Value = result. The split (' < br > < b > < hr > < br > ') [1]. The split (' < br > < br > < hr > ') [0] return value. The strip () except that the return 'No, I' m... Doesn 't! ' print get_random_futurama_quote() print get_random_futurama_quote() #outputs: #get_random_futurama_quote () {} #wrapper 0.02 #wrapper has been used: 1x #The laws of science be a harsh mistress. #get_random_futurama_quote () {} #wrapper 0.01 #wrapper has been used: 2x #Curse you, merciful Poseidon!Copy the code

Python itself provides several decorators: Property, StaticMethod, and so on

  • Django uses decorators to manage caches and view permissions.
  • Twisted uses this to forge inline asynchronous function calls.

Decorators are really versatile.



comments

About the author:coke

Personal home page
My article
14