Python Decorators

Keywords: Python Django

http://book.pythontips.com/en/latest/decorators.html

In the two notes of build in functions (3.6) and python context manager, there are preliminary examples of decorators. This article combines a blog of Colgate University's bull people to systematically explain the role of decorators in Python.

First, put forward a unified concept

Decorators are functions which modify the functionality of other functions. They help to make our code shorter and more Pythonic.

The function of decorators (also called decorators) is to change the way other functions work, which can make the code more concise and python.

2. Next step by step approach to decorator

First of all, functions can return functions, for example:

def hi(name="Leo"):
    def greet():
        return "Congratulations!"

    def welcome():
        return "Welcome!"

    if name == "Leo":
        return greet
    else:
        return welcome

a = hi()
print(a)
# <function greet at 0x7f2143c01500>
# This means that a here is a gret (), because the default name is "Leo", and return is a gret without brackets, which will return a function instead of the result after the function is executed.

print(a())
# Congratulations!
# If you print(a) as above, you only get a greet() function, but if you print(a()), the function will be executed, so what you return is the content of greet() return.

The function can then, of course, be used as an argument to other functions:

def hi():
    return "hi Leo!"

def doSomethingBeforeHi(func):
    print("implement"+func.__name__+"()Call 666 first")
    print(func())

doSomethingBeforeHi(hi)
# Call 666 before executing hi().
# hi Leo!

It is easy to understand that everything in Python can be regarded as an object. Whether it's a custom function or basic data type or a custom class or instance, everything is an object (familiar with it). These objects can be returned as input.

And the core of the decorator is also put forward above: in order to make the code more concise. Because writing all the functions in one function will be very cumbersome, so writing some functions first as the input of another function can make the code more portable.

3. OK, I can write a decorator next.

# coding=utf-8
def decorator(func):
    def doSomethingBeforeFunc():
        print("Do something else before you start.")
        func()
    return doSomethingBeforeFunc

def func():
    print("The real thing to do is here.")

func=decorator(func)
func()
# Do something else before you start.
# The real thing to do is here.

We can see that func function just wanted to print a sentence "what we really want to do is here", but because of the change of its behavior after the processing of decorator(), it printed another sentence. This is because the decorator takes func as the input parameter and return s a function sealed with func. This encapsulated function contains not only the function of func(), but also the function of print("do it Let's have something else before we get down to business. "), so when func=decorator(func) is executed, func is not the original func. In fact, it is dosomethingbeforeffunc().

The common way to call a decorator is not func=decorator(func), but the @ symbol.

The following way is to use @ decorator to call the decorator before defining func, which is equivalent to executing func=decorator(func) after defining func:

# coding=utf-8
def decorator(func):
    def doSomethingBeforeFunc():
        print("Do something else before you start.")
        func()
    return doSomethingBeforeFunc
@decorator 
def func():
    print("The real thing to do is here.")

func()
# Do something else before you start.
# The real thing to do is here.

This code is exactly the same as the above code, but the writing method below is more common and more Pythonic (more unfriendly to newcomers).

IV. a BUG of the above code

What if we print "name" for func after decorator is used in the above code? It's easy to guess that the name of dosomethingbefore func must be obtained, which is generally not what we want.

# coding=utf-8
from functools import wraps
def decorator(func):
    @wraps(func)
    def doSomethingBeforeFunc():
        print("Do something else before you start.")
        func()
    return doSomethingBeforeFunc

@decorator
def func():
    print("The real thing to do is here.")

func()
print(func.__name__)
# Do something else before you start.
# The real thing to do is here.
# func

By introducing the wrap method of functools, the display problem of "name" can be solved. As for the mechanism through which wraps are implemented, the source code is too convoluted to see. In short, wraps allows us to obtain various built-in properties before func() is decorated by a modifier.

V. classic use scenarios of Decorator

A very classic use scenario of decorators in Python is when password verification is required in Django, for example:

# coding=utf-8
from functools import wraps
def require_login(func):
    @wraps(func)
    def new_func(*args, **kwargs):
        auth = request.authorization
        if not auth or check_auth(auth.username, auth.password):
            authenticate()
        return func(*args, **kwargs)
    return new_func

@require_login
def func():
    print("Browsing page...")

The above is just a pseudo code example. django can directly call related modules:

from django.contrib.auth.decorators import login_required

Vi. not only the decorator function, but also the decorator class

The previous examples are all modifier functions, in fact, modifiers can also be classes. As long as a class can be called like a function:

# coding=utf-8
class Decorator(object):
    def __init__(self , func):
        self.func = func
    def __call__(self, *args, **kwargs):  # You can call a class as a function by overriding its call method
        print("Well! Sun thief!")
        return self.func(*args, **kwargs)

@Decorator
def Charge():
    print("Charging to Faye!")

Charge()

 

Posted by PeeJay on Wed, 23 Oct 2019 00:48:49 -0700