Detailed Explanation of Python Function Decorator

  • 2021-12-11 07:46:55
  • OfStack

Directory Usage Scenario Authorization (Authorization) Log (Logging) Decorator with Parameters Embeds Decorator Decorator Class Summary in Function

Decorator (Decorators) is an important part of Python. Simply put: They are functions that modify the functions of other functions. They help to make our code shorter and more Pythonic (Python style). Most beginners don't know where to use them, so I'm going to share the areas where decorators can make your code more concise. First, let's discuss how to write your own decorator.

This is probably one of the most difficult concepts to master. We will only discuss one step at a time, so that you can fully understand it.

1 cut all objects

First, let's understand the functions in Python:


def hi(name="yasoob"):
    return "hi " + name
print(hi())
# output: 'hi yasoob'
#  We can even put 1 A function is assigned to 1 Variables, such as 
greet = hi
#  We are not using parentheses here, because we are not calling hi Function 
#  It's putting it in greet Inside the variable. Let's try to run this 
print(greet())
# output: 'hi yasoob'
#  If we delete the old ones hi Function to see what happens! 
del hi
print(hi())
#outputs: NameError
print(greet())
#outputs: 'hi yasoob'

Defining a function within a function

That's the basics of functions. Let's take your knowledge one step further. In Python, we can define another function within one function:


def hi(name="yasoob"):
    print("now you are inside the hi() function")
    def greet():
        return "now you are in the greet() function"
    def welcome():
        return "now you are in the welcome() function"
    print(greet())
    print(welcome())
    print("now you are back in the hi() function")
hi()
#output:now you are inside the hi() function
#       now you are in the greet() function
#       now you are in the welcome() function
#       now you are back in the hi() function
#  It shows that whenever you call hi(), greet() And welcome() Will be called at the same time. 
#  And then greet() And welcome() Function in hi() Function is inaccessible, such as: 
greet()
#outputs: NameError: name 'greet' is not defined

Now we know that we can define another function in the function. That is, we can create nested functions. Now you need to learn one more point, that is, functions can return functions.

Returns a function from a function

In fact, we don't need to execute another function in one function, we can also return it as output:


def hi(name="yasoob"):
    def greet():
        return "now you are in the greet() function"
    def welcome():
        return "now you are in the welcome() function"
    if name == "yasoob":
        return greet
    else:
        return welcome
a = hi()
print(a)
#outputs: <function greet at 0x7f2143c01500>
# It clearly shows that `a` Now point to hi() Function in the greet() Function 
# Now try this 
print(a())
#outputs: now you are in the greet() function

Look at this code again. In the if/else statement, we return greet and welcome instead of greet () and welcome (). Why that? This is because when you put a pair of parentheses after it, this function will execute; However, if you don't put parentheses after it, it can be passed around and can be assigned to another variable without executing it. Did you catch that? Let me explain a little more details.

When we write a = hi (), hi () is executed, and since the name parameter defaults to yasoob, the function greet is returned. If we change the statement to a = hi (name = "ali"), the welcome function will be returned. We can also print out hi () (), which will output now you are in the greet () function.

Pass a function as an argument to another function


def hi():
    return "hi yasoob!"
def doSomethingBeforeHi(func):
    print("I am doing some boring work before executing hi()")
    print(func())
doSomethingBeforeHi(hi)
#outputs:I am doing some boring work before executing hi()
#        hi yasoob!

Now that you have all the necessary knowledge, take a step forward to learn what a decorator really is. The decorator lets you execute code before and after a function.

Your first decorator

In the previous example, we have actually created a decorator! Now let's modify the last decorator and write a slightly more useful program:


def a_new_decorator(a_func):
    def wrapTheFunction():
        print("I am doing some boring work before executing a_func()")
        a_func()
        print("I am doing some boring work after executing a_func()")
    return wrapTheFunction
def a_function_requiring_decoration():
    print("I am the function which needs some decoration to remove my foul smell")
a_function_requiring_decoration()
#outputs: "I am the function which needs some decoration to remove my foul smell"
a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
#now a_function_requiring_decoration is wrapped by wrapTheFunction()
a_function_requiring_decoration()
#outputs:I am doing some boring work before executing a_func()
#        I am the function which needs some decoration to remove my foul smell
#        I am doing some boring work after executing a_func()

Do you understand? We have just applied the principles we learned before. This is exactly what the decorator in python does! They encapsulate a function and modify its behavior in one way or another. Now you may wonder, we don't use the @ symbol in the code. That's just a short way to generate a decorated function. Here's how we use @ to run the previous code:


@a_new_decorator
def a_function_requiring_decoration():
    """Hey you! Decorate me!"""
    print("I am the function which needs some decoration to "
          "remove my foul smell")
a_function_requiring_decoration()
#outputs: I am doing some boring work before executing a_func()
#         I am the function which needs some decoration to remove my foul smell
#         I am doing some boring work after executing a_func()
#the @a_new_decorator is just a short way of saying:
a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)

I hope you have a basic understanding of how Python decorator works now. If we run the following code, there will be 1 problem:


print(a_function_requiring_decoration.__name__)
# Output: wrapTheFunction

This is not what we want! The Ouput output should be "a_function_requiring_decoration". The function here has been replaced by warpTheFunction. It rewrites the name and comment document of our function (docstring). Fortunately, Python provides us with a simple function to solve this problem, and that is functools. wraps. We modified the previous example to use functools. wraps:


from functools import wraps
def a_new_decorator(a_func):
    @wraps(a_func)
    def wrapTheFunction():
        print("I am doing some boring work before executing a_func()")
        a_func()
        print("I am doing some boring work after executing a_func()")
    return wrapTheFunction
@a_new_decorator
def a_function_requiring_decoration():
    """Hey yo! Decorate me!"""
    print("I am the function which needs some decoration to "
          "remove my foul smell")
print(a_function_requiring_decoration.__name__)
# Output: a_function_requiring_decoration

Much better now. Let's learn some common scenes of decorators next.

Blueprint specification:


from functools import wraps
def decorator_name(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        if not can_run:
            return "Function will not run"
        return f(*args, **kwargs)
    return decorated
@decorator_name
def func():
    return("Function is running")
can_run = True
print(func())
# Output: Function is running
can_run = False
print(func())
# Output: Function will not run

Note: @ wraps accepts a function for decoration and adds the ability to copy function names, comment documents, parameter lists, and so on. This allows us to access the properties of functions before decoration in the decorator.

Usage scenario

Now let's look at where the decorator is particularly dazzling, and how it can make things easier to manage.

Authorization (Authorization)

The decorator can help check whether someone is authorized to use the endpoint of an web application (endpoint). They are widely used in the Flask and Django web frameworks. Here is an example of using decorator-based authorization:


from functools import wraps
def requires_auth(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        auth = request.authorization
        if not auth or not check_auth(auth.username, auth.password):
            authenticate()
        return f(*args, **kwargs)
    return decorated

Log (Logging)

Log is another highlight of decorator application. Here's an example:


def hi(name="yasoob"):
    print("now you are inside the hi() function")
    def greet():
        return "now you are in the greet() function"
    def welcome():
        return "now you are in the welcome() function"
    print(greet())
    print(welcome())
    print("now you are back in the hi() function")
hi()
#output:now you are inside the hi() function
#       now you are in the greet() function
#       now you are in the welcome() function
#       now you are back in the hi() function
#  It shows that whenever you call hi(), greet() And welcome() Will be called at the same time. 
#  And then greet() And welcome() Function in hi() Function is inaccessible, such as: 
greet()
#outputs: NameError: name 'greet' is not defined
0

I'm sure you're already thinking about one other clever use of decorators.

Decorator with parameters

To think about this problem, isn't @ wraps also a decorator? However, it takes 1 argument, just as any ordinary function can do. So, why don't we do the same? This is because, when you use the @ my_decorator syntax, you are applying a wrapper function with a single function as an argument. Remember, everything in Python is an object, and this includes functions! With this in mind, we can write a function that can return a wrapper function under 1.

Embedding decorators in functions

Let's go back to the log example and create a wrapper function that allows us to specify a log file for output:


def hi(name="yasoob"):
    print("now you are inside the hi() function")
    def greet():
        return "now you are in the greet() function"
    def welcome():
        return "now you are in the welcome() function"
    print(greet())
    print(welcome())
    print("now you are back in the hi() function")
hi()
#output:now you are inside the hi() function
#       now you are in the greet() function
#       now you are in the welcome() function
#       now you are back in the hi() function
#  It shows that whenever you call hi(), greet() And welcome() Will be called at the same time. 
#  And then greet() And welcome() Function in hi() Function is inaccessible, such as: 
greet()
#outputs: NameError: name 'greet' is not defined
1

Decorator class

Now we have the logit decorator that can be used in a formal environment, but when some parts of our application are still fragile, exceptions may be something that needs more urgent attention. For example, sometimes you just want to log to 1 file. And sometimes you want to send an email to a problem that comes to your attention, and also keep a log and keep a record. This is a scenario using inheritance, but so far we have only seen the functions used to build the decorator.

Fortunately, classes can also be used to build decorators. Let's now reconstruct logit as a class instead of a function.


def hi(name="yasoob"):
    print("now you are inside the hi() function")
    def greet():
        return "now you are in the greet() function"
    def welcome():
        return "now you are in the welcome() function"
    print(greet())
    print(welcome())
    print("now you are back in the hi() function")
hi()
#output:now you are inside the hi() function
#       now you are in the greet() function
#       now you are in the welcome() function
#       now you are back in the hi() function
#  It shows that whenever you call hi(), greet() And welcome() Will be called at the same time. 
#  And then greet() And welcome() Function in hi() Function is inaccessible, such as: 
greet()
#outputs: NameError: name 'greet' is not defined
2

This implementation has an added advantage, in that it is neater than nesting functions, and wrapping a function still uses the same syntax as the previous one:


@logit()
def myfunc1():
    pass

Now, let's create a subclass for logit to add the functionality of email (although the topic of email will not be expanded here).


def hi(name="yasoob"):
    print("now you are inside the hi() function")
    def greet():
        return "now you are in the greet() function"
    def welcome():
        return "now you are in the welcome() function"
    print(greet())
    print(welcome())
    print("now you are back in the hi() function")
hi()
#output:now you are inside the hi() function
#       now you are in the greet() function
#       now you are in the welcome() function
#       now you are back in the hi() function
#  It shows that whenever you call hi(), greet() And welcome() Will be called at the same time. 
#  And then greet() And welcome() Function in hi() Function is inaccessible, such as: 
greet()
#outputs: NameError: name 'greet' is not defined
4

From now on, @ email_logit will have the same effect as @ logit, but on the basis of logging, one more email will be sent to the administrator.

Summarize

This article is here, I hope to give you help, but also hope that you can pay more attention to this site more content!


Related articles: