Python Basic Topics - Teach you how to use decorators

Python
Usage guide for Python decorators

Author : Li Juncai (jcLee95) : https://blog.csdn.net/qq_28550263?spm=1001.2101.3001.5343
Email: [email protected]
Address of this article : https://blog.csdn.net/qq_28550263/article/details /114963007


Guide:
Python decorator allows us to extend the functionality of a function by adding a decorator before the function definition without modifying the original function code. This article will introduce the basic concepts and usage of decorators, and provide sample code to illustrate their application scenarios and practical uses.

Writing convention: Out[i]:Indicates the output of a certain piece of code in the terminal.


1 Overview

1.1 What is a Python decorator

Let me say a conclusion first:

A decorator is a function that takes a function as an argument

A decorator can also be called a function wrapper , which is a function that takes a function as a parameter. Its function is to wrap a function that is passed into the wrapper as a parameter, adding more functions to it without knowing the internal implementation of the wrapped function.

[Example]:
There is an unwrapped function, which looks like this

def a_function():
    print("-劳资是普通函数函数-")
    
a_function()        # 调用执行这个函数

Out[i]

'-劳资是普通函数函数-'

Now Li Hua has written a wrapper a_decoratorfor you, you don't need to care about its implementation, you just need to use it to decorate the above functions. Then you just write it like this, and run it:

@a_decorator
def a_function():
    print("-劳资是被装饰函数-")

Out[i]:

在被装饰函数前面做点事
-劳资是被装饰函数-
在被装饰函数后面做点事

From another perspective, the wrapper is a function integration mechanism , which has many similarities with the class inheritance mechanism but also has differences. In inheritance of object-oriented programming classes:

  • The subclass inherits the parent class, the subclass accepts the parent class as a parameter, and the subclass obtains the methods and properties of the parent class. At the same time, the subclass must implement some other functions based on the base entropy of the parent class, so that the subclass is stronger than the parent class in a certain way so that the subclass can complete more specific work .
  • The wrapper inherits its functions from the wrapped function, and at the same time, the wrapper needs to implement more functions based on the functions of the wrapped function, making the wrapper more powerful so that it can realize more functions .

But it is different from the inheritance mechanism of classes:

  • In class inheritance, we generally implement the parent class first and then the subclass, but in the usage of the wrapper, it is just the opposite, first implementing the wrapper and then implementing the wrapped function. Therefore, it is said that class inheritance is from parent to child , while function decoration is from child to parent .

1.2 Benefits of using Python decorators

Decorators are widely used in Python to modify and extend functions and classes. Its functions and advantages include:

  • Extend function functions: Decorators can add additional functions to functions without modifying the function code, such as logging, performance statistics, input validation, etc.
  • Code reuse and simplification: Decorators can encapsulate some common functions into decorator functions, and then apply them to multiple functions through decorators to avoid writing the same code repeatedly.
  • Decoupling and enhanced readability: Decorators can separate the core logic of a function from auxiliary functions, making the code more readable and maintainable.
  • Do not change the original code structure: modifying the function through the decorator will not modify the code structure of the original function, and at the same time allow us to dynamically modify the function behavior at runtime.

2. Preliminary study of decorators

2.1 Basic syntax of decorators

In this section, we just briefly look at the simplest syntax of the decorator, and make the necessary pavement for the next chapter to experience the decorator in the actual framework. The basic syntax of a decorator is to use @the notation to apply the decorator function to the target function. The decorator function accepts the target function as a parameter and modifies or extends the target function inside the function.

Here is an example of a simple decorator:

def decorator_func(func):
    def wrapper():
        print("函数执行前")
        func()
        print("函数执行后")
    return wrapper

@decorator_func
def hello():
    print("Hello, world!")

hello()

In the above example, we defined a decorator function decorator_func which takes a function as an argument and defined an inner function wrapper. Inside the wrapper function, we add extra functionality and print some information before and after function execution.

We apply a decorator to the hello function by using @decorator_func before the definition of the hello function. When the hello function is called, the wrapper function modified by the decorator is actually called, so as to achieve the purpose of adding additional functions before and after function execution.

2.2 Application scenarios of decorators

There are many application scenarios for decorators in actual development. The following are some common application scenarios:

  • Logging: through the decorator, it is convenient to record the call information, parameters and return value of the function for debugging and logging.
  • Timing Statistics: Decorators can be used to measure the execution time of functions for performance analysis and optimization.
  • Input verification: The input parameters of the function can be verified through the decorator to ensure the legality and validity of the parameters.
  • Cache results: Decorators can be used to cache the calculation results of functions to improve the execution efficiency of functions.
  • Authorization and authentication: Decorators can be used to authorize and authenticate functions, and restrict access to functions.

The above are just some examples of application scenarios of decorators. In fact, we can customize and combine decorators according to specific needs to achieve more complex functions and logic.

3. Experience: Decorators in the Django framework

3.1 About this section

Django is one of the most important heavyweight . Some built-in decorators are provided in the Django framework to achieve different functions and requirements. The following will take you to experience the commonly used decorators and their usage in Django. This program does not require you to have a comprehensive grasp of decorators, but to let you, who are half-understood, have more perceptual knowledge of decorators from a few simple examples.

3.2 @login_requiredDecorators

@login_requiredA decorator is used to restrict access to a view function only to logged-in users. If a user who is not logged in visits the decorated view function, he will be redirected to the login page.

For example:

from django.contrib.auth.decorators import login_required
from django.shortcuts import render

@login_required
def my_view(request):
    # 仅允许已登录用户访问的视图函数
    return render(request, 'my_view.html')

In the above example, we restrict access to the view function to only logged-in users by adding @login_requireda decorator my_view. If a user who is not logged in tries to access this view function, Django will redirect them to the login page.

3.3 @permission_requiredDecorators

The @permission_required decorator is used to restrict access to a view function only to users with specified permissions. If the user does not have the appropriate permissions, an error page will be displayed.

For example:

from django.contrib.auth.decorators import permission_required
from django.shortcuts import render

@permission_required('polls.can_vote')
def my_view(request):
    # 仅允许拥有 'polls.can_vote' 权限的用户访问的视图函数
    return render(request, 'my_view.html')

In the above example, @permission_required('polls.can_vote')we restrict 'polls.can_vote'access to the view function only to users with the my_viewpermission by adding the decorator before the view function definition. If the user does not have the appropriate permissions, an error page will be displayed.

3.4 @csrf_exemptDecorators

@csrf_exemptDecorator for turning off Cross-Site Request Forgery (CSRF) protection in view functions . By using this decorator, it is possible to allow non- CSRF protected POST requests to access view functions.

For example:

from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse

@csrf_exempt
def my_view(request):
    # 允许未经 CSRF 保护的 POST 请求访问的视图函数
    if request.method == 'POST':
        return HttpResponse('POST request')
    else:
        return HttpResponse('GET request')

In the above example, @csrf_exemptwe allow non-CSRF protected POST requests to access the view my_viewfunction by adding a decorator before the view function definition. In this view function, we return different responses depending on the method of the request.

3.5 @cache_pageDecorators

@cache_pageThe decorator is used to cache the output of the entire view function, thereby improving the response speed of the page. The cache time can be specified to control the validity period of the cache.

For example:

from django.views.decorators.cache import cache_page
from django.shortcuts import render

@cache_page(60 * 10)  # 缓存 10 分钟
def my_view(request):
    # 缓存整个视图函数的输出结果
    return render(request, 'my_view.html')

In the above example, @cache_page(60 * 10)we my_viewcache the output of the view function by adding the decorator before the view function definition, and the validity period is 10minutes . In the view function, we return the rendered template.

4. Function decorator

4.1 Decorator principle and basic syntax

In the previous chapters, we briefly understood and used decorators. Starting from this section, we will explain it comprehensively. A decorator is a Python syntax feature that allows us to extend the behavior of a function by adding additional functionality without modifying the original function code. A decorator is essentially a function that takes a function as an argument and returns a new function or callable.

The syntax structure of a decorator is as follows:

def decorator_func(original_func):
    def wrapper(*args, **kwargs):
        # 在调用原始函数之前执行的代码
        result = original_func(*args, **kwargs)
        # 在调用原始函数之后执行的代码
        return result
    return wrapper

In the above code, we define a decorator function called decorator_func, which accepts an original_func parameter, representing the original function to be decorated. Inside the decorator, we define a wrapper function called wrapper, which is responsible for executing additional code logic before and after calling the original function.

The wrapper function inside the decorator function uses *argsand **kwargsto accept any number of positional and keyword arguments and pass them to the original function. This way, we can adapt functions with different parameters without affecting the original function signature.

To use a decorator we need to apply it to the target function, we can use @the symbol to place the decorator before the function definition, for example:

@decorator_func
def my_function():
    # 函数的代码逻辑

With this syntax, we apply decorator_functhe decorator to my_functionthe function. This is equivalent to:

my_function = decorator_func(my_function)

Now, my_functionwhenever , we're actually calling wrapperthe function returned by the decorator. In wrappera function , we can add extra functionality, modify the behavior of the function, and then call the original function and return its result.

4.2 Applying decorators to modify function behavior

Decorators provide a flexible way to modify the behavior of functions. We can add additional functionality through decorators, such as logging, input validation, performance testing, etc. This chapter describes how to use decorators to modify the behavior of functions.

For example:

# 一个添加日志记录功能的装饰器
def log_decorator(original_func):
    def wrapper(*args, **kwargs):
        print(f"调用函数 {
      
      original_func.__name__}")
        result = original_func(*args, **kwargs)
        print(f"函数 {
      
      original_func.__name__} 调用完毕")
        return result
    return wrapper

In the above example, we defined a decorator function log_decoratorcalled . It prints a log before calling the original function, then calls the original function, and finally prints another log. Now, let's decorate a function with this decorator:

@log_decorator
def add_numbers(a, b):
    return a + b

The decorator automatically adds logging functionality when we call the add_numbers function:

result = add_numbers(10, 5)

Out[]:

调用函数 add_numbers
函数 add_numbers 调用完毕
print(result)

Out[]: 15

With decorators, we can add additional functionality without modifying the original function code.

Another example:

import time

def performance_decorator(original_func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = original_func(*args, **kwargs)
        end_time = time.time()
        execution_time = end_time - start_time
        print(f"函数 {
      
      original_func.__name__} 的执行时间为:{
      
      execution_time}秒")
        return result
    return wrapper

In the above example, we defined a decorator function performance_decoratorcalled . It will record the start time before calling the original function, calculate the execution time after calling the original function, and print it. Now, let's decorate a function with this decorator:

@performance_decorator
def calculate_factorial(n):
    factorial = 1
    for i in range(1, n+1):
        factorial *= i
    return factorial

When we call the calculate_factorial function, the decorator will automatically calculate the execution time and output:

result = calculate_factorial(10)

Out[]: The execution time of the function calculate_factorial is: 6.4373016357421875e-06 seconds

print(result)

Out[]:3628800

Through this example we can see that by using decorators we can easily add additional functionality such as performance testing without modifying the code of the original function.

4.3 Applying decorators to modify function behavior

Decorators with parameters are a more flexible and customizable form of decorator. It allows us to control the behavior of the decorator by adding parameters to the decorator function. In this chapter, we discuss how to write decorators that take parameters and provide some examples to illustrate their usage.

For example:

def repeat(num_times):
    def decorator_func(original_func):
        def wrapper(*args, **kwargs):
            for _ in range(num_times):
                result = original_func(*args, **kwargs)
            return result
        return wrapper
    return decorator_func

In this example we define a decorator function repeatcalled that takes one argument num_times. The decorator function defines another function inside decorator_func, which is the real decorator. decorator_funcThe inner wrapperfunction will repeatedly call the original function num_timestimes.

We can decorate a function with a parameterized decorator as follows:

@repeat(num_times=3)
def greet(name):
    print(f"Hello, {
      
      name}!")

greet("Alice")

Out[]:

Hello, Alice!
Hello, Alice!
Hello, Alice!

By adding the parameter =3 to the decorator num_times, we can specify the number of times greetthe function will be called. In this way, the print statement in the function body will be repeated three times.

Another example:

import time

def performance_decorator(unit):
    def decorator_func(original_func):
        def wrapper(*args, **kwargs):
            start_time = time.time()
            result = original_func(*args, **kwargs)
            end_time = time.time()
            execution_time = end_time - start_time
            print(f"函数 {
      
      original_func.__name__} 的执行时间为:{
      
      execution_time}{
      
      unit}")
            return result
        return wrapper
    return decorator_func

In this example we define a decorator function performance_decoratorcalled that takes a parameter unitspecifying the unit of execution time. decorator_funcThe internal wrapperfunction calculates the function execution time and prints it with the specified unit.

We can use it like this:

@performance_decorator(unit="秒")
def calculate_power(base, exponent):
    result = base ** exponent
    time.sleep(1)  # 模拟函数执行的时间
    return result

When we call calculate_powerthe function , the decorator will automatically calculate the execution time and output it in seconds.

result = calculate_power(2, 10)

Out[]: The execution time of the function calculate_power is: 1.0002830028533936 seconds

print(result)  # 输出:1024

5. Class decorators

Decorators can also be implemented using the class syntax in Python. This chapter describes how to use class decorators and explains the difference between class decorators and function decorators.

5.1 Structure and usage of class decorators

A class decorator is a method of using a class to implement the decorator function. Unlike a function decorator, a class decorator can perform additional logic when instantiated and can track state.

For example:

class DecoratorClass:
    def __init__(self, original_func):
        self.original_func = original_func

    def __call__(self, *args, **kwargs):
        # 在调用原始函数之前执行的代码
        result = self.original_func(*args, **kwargs)
        # 在调用原始函数之后执行的代码
        return result

In this example, we define a class decorator DecoratorClasscalled . It takes one argument original_funcrepresenting the original function being decorated. __call__A method in a class decorator defines the behavior of the decorator instance when called, similar to wrappera function in a function decorator.

After defining a class decorator, we can use it like this:

@DecoratorClass
def greet(name):
    print(f"Hello, {
      
      name}!")

By DecoratorClassapplying as a decorator, we instantiate it and pass the decorated function as an argument to the constructor. At this point, __call__the method will be called.

5.2 Stateful class decorators

In Python, a stateful class decorator is a special type of decorator that keeps some state information inside the decorator instance. This means that the decorator can track and update the state every time the decorated function is called.

Normally, function decorators are stateless, they just execute some logic before and after the function call without remembering the previous state. However, sometimes we want the decorator to maintain some state, such as counters, caches, etc. This is what stateful class decorators do.

Stateful class decorators are usually implemented by implementing __init__()the and __call__()methods of a class. In __init__()the method , we can initialize the state variables and __call__()execute the logic of the decorator in the method.

For example:

class Counter:
    def __init__(self, original_func):
        self.original_func = original_func
        self.count = 0

    def __call__(self, *args, **kwargs):
        self.count += 1
        print(f"函数 {
      
      self.original_func.__name__} 被调用了 {
      
      self.count} 次")
        result = self.original_func(*args, **kwargs)
        return result

In this example, Counterthe class decorator will save the decorated original function self.original_funcin and initialize the counter self.countas when it is initialized 0. Each time the decorated function is called, __call__()the method increments the counter and prints the number of times the function was called.

We can use this decorator like this:

@Counter
def add_numbers(a, b):
    return a + b

With such a decoration, when we call add_numbersthe function , the decorator will automatically record the number of times the function is called and output the count information.

result = add_numbers(10, 5)

Out[]: The function add_numbers was called 1 time

print(result)

Out[]:15

result = add_numbers(8, 2)

Out[]:The function add_numbers was called 2 times

print(result)

Out[]:10

6. Combination of decorators

However, in actual development, we may need to apply multiple decorators at the same time to achieve more complex functions or modify multiple aspects of the function. This chapter describes how to combine multiple decorators, and the order in which they are executed. An example of a composite decorator is as follows:

def decorator1(original_func):
    def wrapper(*args, **kwargs):
        # 装饰器1的逻辑
        result = original_func(*args, **kwargs)
        # 装饰器1的逻辑
        return result
    return wrapper

def decorator2(original_func):
    def wrapper(*args, **kwargs):
        # 装饰器2的逻辑
        result = original_func(*args, **kwargs)
        # 装饰器2的逻辑
        return result
    return wrapper

Now we combine these two decorators and apply them to a function:

@decorator1
@decorator2
def greet(name):
    print(f"Hello, {
      
      name}!")

In this case, greetthe function will have the decorator applied first decorator2, and the decorator applied second decorator1. This means that the execution order of greetthe functions will follow the stacking order of the decorators .

Now we call this greetfunction :

greet("Jack")

Its execution process is as follows:

# decorator1 的逻辑
# decorator2 的逻辑
Hello, Jack!
# decorator2 的逻辑
# decorator1 的逻辑

That is, first the decorator decorator2's logic is executed, then the decorator decorator1's logic, and finally the original function greet's logic. After the function is executed, the logic of the decorator is executed in reverse order.

Guess you like

Origin blog.csdn.net/qq_28550263/article/details/114963007