Python基础专题 - 教你使用装饰器

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: Python基础专题 - 教你使用装饰器

PythonPython 装饰器的用法指导


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 的逻辑。在函数执行完毕后,装饰器的逻辑又按照相反的顺序执行。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
9天前
|
Python
深入理解Python装饰器:从入门到实践####
本文旨在通过简明扼要的方式,为读者揭开Python装饰器的神秘面纱,从基本概念、工作原理到实际应用场景进行全面解析。不同于常规的摘要仅概述内容概要,本文将直接以一段精炼代码示例开篇,展示装饰器如何优雅地增强函数功能,激发读者探索兴趣,随后深入探讨其背后的机制与高级用法。 ####
39 11
|
6天前
|
设计模式 缓存 开发者
深入浅出Python装饰器
【10月更文挑战第39天】本文将通过浅显易懂的语言和生动的比喻,带你探索Python中一个神奇而又强大的特性——装饰器。我们将一起揭开装饰器的神秘面纱,了解它的工作原理,并通过实际代码示例学习如何应用它来美化我们的代码。无论你是编程新手还是有经验的开发者,这篇文章都将为你打开一扇新的大门,让你的代码更加优雅和高效。
|
6天前
|
缓存 测试技术 数据库
深入理解Python中的装饰器
在本文中,我们将探讨Python语言中一个强大而灵活的特性——装饰器。装饰器允许开发者在不修改原有函数或方法代码的情况下增加额外的功能,这大大提高了代码的复用性和可读性。通过具体示例和应用场景的讲解,本篇文章旨在为读者提供一个关于如何使用装饰器的全面指南,包括装饰器的定义、使用场景、以及如何自定义装饰器等内容。
|
10天前
|
设计模式 Python
掌握Python中的装饰器
【10月更文挑战第34天】装饰器是Python中一种强大的工具,它允许我们在不修改原函数代码的情况下增加其功能。本文通过简单易懂的语言和实例,引导你理解装饰器的概念、种类及其应用,帮助你在编程实践中灵活使用这一高级特性。
|
7天前
|
缓存 监控 测试技术
Python中的装饰器:功能扩展与代码复用的利器###
本文深入探讨了Python中装饰器的概念、实现机制及其在实际开发中的应用价值。通过生动的实例和详尽的解释,文章展示了装饰器如何增强函数功能、提升代码可读性和维护性,并鼓励读者在项目中灵活运用这一强大的语言特性。 ###
|
10天前
|
缓存 开发者 Python
探索Python中的装饰器:简化代码,增强功能
【10月更文挑战第35天】装饰器在Python中是一种强大的工具,它允许开发者在不修改原有函数代码的情况下增加额外的功能。本文旨在通过简明的语言和实际的编码示例,带领读者理解装饰器的概念、用法及其在实际编程场景中的应用,从而提升代码的可读性和复用性。
|
5天前
|
设计模式 缓存 开发框架
Python中的装饰器:从入门到实践####
本文深入探讨了Python中装饰器的工作原理与应用,通过具体案例展示了如何利用装饰器增强函数功能、提高代码复用性和可读性。读者将学习到装饰器的基本概念、实现方法及其在实际项目开发中的实用技巧。 ####
18 3
|
6天前
|
Python
探索Python中的装饰器:简化代码,提升效率
【10月更文挑战第39天】在编程的世界中,我们总是在寻找使代码更简洁、更高效的方法。Python的装饰器提供了一种强大的工具,能够让我们做到这一点。本文将深入探讨装饰器的基本概念,展示如何通过它们来增强函数的功能,同时保持代码的整洁性。我们将从基础开始,逐步深入到装饰器的高级用法,让你了解如何利用这一特性来优化你的Python代码。准备好让你的代码变得更加优雅和强大了吗?让我们开始吧!
14 1
|
11天前
|
设计模式 缓存 监控
Python中的装饰器:代码的魔法增强剂
在Python编程中,装饰器是一种强大而灵活的工具,它允许程序员在不修改函数或方法源代码的情况下增加额外的功能。本文将探讨装饰器的定义、工作原理以及如何通过自定义和标准库中的装饰器来优化代码结构和提高开发效率。通过实例演示,我们将深入了解装饰器的应用,包括日志记录、性能测量、事务处理等常见场景。此外,我们还将讨论装饰器的高级用法,如带参数的装饰器和类装饰器,为读者提供全面的装饰器使用指南。
|
7天前
|
存储 缓存 监控
掌握Python装饰器:提升代码复用性与可读性的利器
在本文中,我们将深入探讨Python装饰器的概念、工作原理以及如何有效地应用它们来增强代码的可读性和复用性。不同于传统的函数调用,装饰器提供了一种优雅的方式来修改或扩展函数的行为,而无需直接修改原始函数代码。通过实际示例和应用场景分析,本文旨在帮助读者理解装饰器的实用性,并鼓励在日常编程实践中灵活运用这一强大特性。