likes
comments
collection
share

Python 装饰器的用法指导

作者站长头像
站长
· 阅读数 15
Python Python 装饰器的用法指导

作者李俊才 (jcLee95)blog.csdn.net/qq_28550263… 邮箱 :291148484@163.com 本文地址blog.csdn.net/qq_28550263…


导读: Python 装饰器它允许我们在不修改原有函数代码的情况下,通过在函数定义前添加修饰器来扩展函数的功能。本文将介绍装饰器的基本概念和用法,并提供示例代码来说明其应用场景和实际用途。

写作约定:Out[i]:表示某段代码在终端的输出。

@[TOC](

目 录
)


1. 概述

1.1 什么是 Python 装饰器

先说一个结论:

装饰器是一个以函数为参数的函数

装饰器也可以叫函数包装器,是一个以函数为参数的函数。它的功能就是将一个作为参数传入包装器的函数进行包装,为其添加更多的功能同时不需要了解被包装函数的内部实现。

【引例】: 有一个没有被包装的函数,看起来是这样的

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

Out[i]

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

现在李华写好了一个包装器a_decorator给你,你不需要关心它的实现,只需要使用它来装饰上面的函数。那么你就而已这样写,然后并运行它:

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

Out[i]:

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

从另外一个角度理解,包装器是一种函数的集成机制,与类的继承机制有很多相似之处却也有不同。在面向对象编程类的继承中:

  • 子类继承父类,子类接受父类作为参数,子类即获得父类的方法和属性。同时子类必须在父类的基础熵实现一些其它的功能使得子类在某个方面比父类更强大以至于子类能够完成更多具体的工作
  • 包装器从被包装函数那继承它的功能,同时包装器又需要在被包装函数功能的基础上实现更多的功能,使得包装器更加强大以至于能够实现更多功能

但是与类的继承机制又有区别:

  • 在类的继承中一般我们是先实现父类后实现子类,而在包装器的用法中则正好相反,是先实现包装器后实现被包装的函数。因此说类继承是由父到子,而函数装饰是子到父

1.2 使用 Python 装饰器的好处

装饰器在 Python 中被广泛应用于函数和类的修饰和扩展。它的作用和优势包括:

  • 扩展函数功能:装饰器可以在不修改函数代码的情况下,为函数添加额外的功能,例如日志记录、性能统计、输入验证等。
  • 代码复用和简化:装饰器可以将一些通用的功能封装成装饰器函数,然后通过修饰器的方式应用到多个函数中,避免重复编写相同的代码。
  • 解耦和增强可读性:装饰器可以将函数的核心逻辑和辅助功能分离,使代码更具可读性和可维护性。
  • 不改变原有代码结构:通过装饰器修饰函数,不会修改原有函数的代码结构,同时允许我们在运行时动态地修改函数行为。

2. 装饰器初探

2.1 装饰器的基本语法

这一节我们仅仅简单地看一下装饰器最简单的语法,为下一章体验实际框架中的装饰器做必要的铺垫。装饰器的基本语法是使用 @ 符号将装饰器函数应用于目标函数。装饰器函数接受目标函数作为参数,并在函数内部对目标函数进行修改或扩展。

下面是一个简单的装饰器示例:

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

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

hello()

在上述示例中,我们定义了一个装饰器函数 decorator_func,它接受一个函数作为参数,并定义了一个内部函数 wrapper。在 wrapper 函数内部,我们添加了额外的功能,并在函数执行前后打印一些信息。

通过在 hello 函数定义前使用 @decorator_func,我们将 hello 函数应用了装饰器。当调用 hello 函数时,实际上是调用了经过装饰器修饰后的 wrapper 函数,从而实现了在函数执行前后添加额外功能的目的。

2.2 装饰器的应用场景

装饰器在实际开发中有多种应用场景,以下是一些常见的应用场景:

  • 记录日志:通过装饰器可以方便地记录函数的调用信息、参数和返回值,用于调试和日志记录。
  • 计时统计:装饰器可以用于测量函数的执行时间,用于性能分析和优化。
  • 输入验证:通过装饰器可以对函数的输入参数进行验证,确保参数的合法性和有效性。
  • 缓存结果:装饰器可以用于缓存函数的计算结果,提高函数的执行效率。
  • 授权和身份验证:装饰器可以用于对函数进行授权和身份验证,限制函数的访问权限。

以上只是一些装饰器的应用场景示例,实际上,我们可以根据具体需求自定义和组合装饰器,以实现更复杂的功能和逻辑。

3. 体验:Django 框架中的装饰器

3.1关于本节

Django 是 Python 语言生态中最重要的 重量级 Web 框架 没有之一。 在 Django 框架中提供了一些内置的装饰器,用于实现不同的功能和需求。下面将带大家一起体验一下 Django 中常用的装饰器及其用法。本节目的并不需要你已经全面掌握装饰器,而是让似懂非懂的你,从几个简单的例子中对装饰器有更多的感性认识。

3.2 @login_required 装饰器

@login_required 装饰器用于限制只有已登录用户才能访问某个视图函数。如果未登录用户访问被装饰的视图函数,将会被重定向到登录页面。

例如:

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')

在上述示例中,我们通过在视图函数定义前添加 @login_required 装饰器,限制了只有已登录的用户才能访问 my_view 视图函数。如果未登录用户尝试访问该视图函数,Django 会将其重定向到登录页面。

3.3 @permission_required 装饰器

@permission_required 装饰器用于限制只有拥有指定权限的用户才能访问某个视图函数。如果用户没有相应权限,将会显示一个错误页面。

例如:

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')

在上述示例中,我们通过在视图函数定义前添加 @permission_required('polls.can_vote') 装饰器,限制了只有拥有 'polls.can_vote' 权限的用户才能访问 my_view 视图函数。如果用户没有相应的权限,将会显示一个错误页面。

3.4 @csrf_exempt 装饰器

@csrf_exempt 装饰器用于在视图函数中关闭 跨站请求伪造(CSRF) 保护。通过使用该装饰器,可以允许未经 CSRF 保护的 POST 请求访问视图函数。

例如:

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')

在上述示例中,我们通过在视图函数定义前添加 @csrf_exempt 装饰器,允许未经 CSRF 保护的 POST 请求访问 my_view 视图函数。在该视图函数中,我们根据请求的方法返回不同的响应。

3.5 @cache_page 装饰器

@cache_page 装饰器用于缓存整个视图函数的输出结果,从而提高页面的响应速度。可以指定缓存的时间,以控制缓存的有效期。

例如:

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')

在上述示例中,我们通过在视图函数定义前添加 @cache_page(60 * 10) 装饰器,将 my_view 视图函数的输出结果缓存起来,有效期为 10 分钟。在该视图函数中,我们返回渲染后的模板。

4. 函数装饰器

4.1 装饰器原理与基本语法

前面的章节我们简单地了解和使用了装饰器,从本节开始,我们全面讲解它。装饰器是一种Python语法特性,它允许我们在不修改原始函数代码的情况下,通过添加额外的功能来扩展函数的行为。装饰器本质上是一个函数,它接受一个函数作为参数,并返回一个新的函数或可调用对象。

装饰器的语法结构如下所示:

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

上述代码中,我们定义了一个名为 decorator_func 的装饰器函数,它接受一个 original_func 参数,代表被装饰的原始函数。在装饰器内部,我们定义了一个名为 wrapper 的包装函数,它负责在调用原始函数之前和之后执行额外的代码逻辑。

装饰器函数内部的 wrapper 函数使用 *args**kwargs 来接受任意数量的位置参数和关键字参数,并将它们传递给原始函数。这样,我们可以在不影响原始函数签名的情况下,适应不同参数的函数。

要使用装饰器我们需要将其应用于目标函数,可以使用 @ 符号将装饰器放置在函数定义之前,例如:

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

通过这样的语法,我们将 decorator_func 装饰器应用于 my_function 函数。这也就相当于:

my_function = decorator_func(my_function)

现在,每当我们调用 my_function 时,实际上是调用装饰器返回的 wrapper 函数。在 wrapper 函数中,我们可以添加额外的功能、修改函数的行为,然后调用原始函数,并返回其结果。

4.2 应用装饰器修改函数行为

装饰器提供了一种灵活的方式来修改函数的行为。我们可以通过装饰器添加额外的功能,例如日志记录、输入验证、性能测试等。本章将介绍如何使用装饰器来修改函数的行为。

例如:

# 一个添加日志记录功能的装饰器
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

在上述示例中,我们定义了一个名为 log_decorator 的装饰器函数。它在调用原始函数之前打印一条日志,然后调用原始函数,最后再打印一条日志。现在,让我们使用该装饰器来装饰一个函数:

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

当我们调用 add_numbers 函数时,装饰器会自动添加日志记录的功能:

result = add_numbers(10, 5)

Out[]:

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

Out[]: 15

通过装饰器,我们可以在不修改原始函数代码的情况下,增加额外的功能。

又如:

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

在上述示例中,我们定义了一个名为 performance_decorator 的装饰器函数。它会在调用原始函数之前记录起始时间,在调用原始函数之后计算执行时间,并打印出来。现在,让我们使用该装饰器来装饰一个函数:

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

当我们调用 calculate_factorial 函数时,装饰器会自动计算执行时间并输出:

result = calculate_factorial(10)

Out[]:函数 calculate_factorial 的执行时间为:6.4373016357421875e-06秒

print(result)

Out[]:3628800

通过这个例子我们可以看到,通过使用装饰器我们可以轻松地添加性能测试等额外的功能而无需修改原始函数的代码。

4.3 应用装饰器修改函数行为

带参数的装饰器是一种更加灵活和可定制的装饰器形式。它允许我们通过在装饰器函数上添加参数,来控制装饰器的行为。在本章中,我们将讨论如何编写带参数的装饰器,并提供一些示例来说明其用法。

例如:

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

本例中我们定义了一个名为 repeat 的装饰器函数,它接受一个参数 num_times。装饰器函数内部定义了另一个函数 decorator_func,它才是真正的装饰器。decorator_func 内部的 wrapper 函数会重复调用原始函数 num_times 次。

我们可以使用带参数的装饰器来装饰一个函数如下:

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

greet("Alice")

Out[]:

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

通过在装饰器上添加参数 num_times=3,我们可以指定 greet 函数被调用的次数。这样,函数体内的打印语句将会重复执行三次。

又如:

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

本例中我们定义了一个名为 performance_decorator 的装饰器函数,它接受一个参数 unit,用于指定执行时间的单位。decorator_func 内部的 wrapper 函数会计算函数执行时间,并在打印时附上指定的单位。

我们可以这样使用:

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

当我们调用 calculate_power 函数时,装饰器会自动计算执行时间并输出,单位为秒。

result = calculate_power(2, 10)

Out[]:函数 calculate_power 的执行时间为:1.0002830028533936秒

print(result)  # 输出:1024

5. 类装饰器

装饰器同样可以通过 Python 中的类(class)语法来实现。本章将介绍如何使用类装饰器,并解释类装饰器与函数装饰器的区别。

5.1 类装饰器的结构和用法

类装饰器是一种使用类来实现装饰器功能的方法,与函数装饰器不同的是 类装饰器 可以在实例化时执行额外的逻辑,并且可以跟踪状态。

例如:

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

这个例子中,我们定义了一个名为 DecoratorClass 的类装饰器。它接受一个参数 original_func,代表被装饰的原始函数。类装饰器中的 __call__ 方法定义了装饰器实例被调用时的行为,类似于函数装饰器中的 wrapper 函数。

定义好类装饰器以后,我们可以这样来使用它:

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

通过将 DecoratorClass 应用为装饰器,我们将其实例化,并将被装饰的函数作为参数传递给构造函数。此时,类装饰器的 __call__ 方法会被调用。

5.2 带状态的类装饰器

在Python中,带状态的类装饰器是一种特殊类型的装饰器,它在装饰器实例中保持一些状态信息。这意味着每次调用被装饰的函数时,装饰器可以跟踪和更新状态。

通常情况下,函数装饰器是无状态的,它们只是在函数调用前后执行一些逻辑,而没有记住之前的状态。但是,有时我们希望装饰器能够保持某种状态,例如计数器、缓存等。这就是带状态的类装饰器的作用。

带状态的类装饰器通常是通过实现类的__init__()__call__() 方法来实现的。在 __init__() 方法中,我们可以初始化状态变量,并在 __call__() 方法中执行装饰器的逻辑。

例如:

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

本例中,Counter 类装饰器在初始化时会将被装饰的原始函数保存在 self.original_func 中,并初始化计数器 self.count0。在每次调用被装饰的函数时,__call__() 方法会递增计数器的值,并打印出函数被调用的次数。

我们可以这样来使用这个装饰器:

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

有了这样的装饰,当我们调用 add_numbers 函数时,装饰器会自动记录函数被调用的次数,并输出计数信息。

result = add_numbers(10, 5)

Out[]: 函数 add_numbers 被调用了 1 次

print(result)

Out[]:15

result = add_numbers(8, 2)

Out[]:函数 add_numbers 被调用了 2 次

print(result)

Out[]:10

6. 装饰器的组合

然而,在实际开发中,我们可能需要同时应用多个装饰器,以实现更复杂的功能或修改函数的多个方面。本章将介绍如何组合多个装饰器,以及它们的执行顺序。一个组合装饰器的例子如下:

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

现在我们组合这两个装饰器,并将它们应用到一个函数上:

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

本例中,greet 函数将先应用 装饰器decorator2,然后再应用 装饰器decorator1。这意味着 greet 函数的执行顺序将 按照装饰器的堆叠顺序 进行。

现在我们调用这个被多个装饰器装饰的 greet 函数:

greet("Jack")

其执行过程如:

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

也就是,首先 装饰器decorator2 的逻辑被执行,然后是 装饰器decorator1 的逻辑,最后是原始函数 greet 的逻辑。在函数执行完毕后,装饰器的逻辑又按照相反的顺序执行。