Python 装饰器的用法指导

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 本文详细介绍 Python 编程语言中的装饰器原理及其用法。

Python 装饰器的用法指导


作者李俊才 (jcLee95)https://blog.csdn.net/qq_28550263?spm=1001.2101.3001.5343

邮箱 :291148484@163.com

本文地址https://blog.csdn.net/qq_28550263/article/details/114963007


导读:

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

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

目 录



1. 概述

1.1 什么是 Python 装饰器

先说一个结论:

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

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

【引例】:

有一个没有被包装的函数,看起来是这样的

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

Out[i]

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

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

@a_decoratordefa_function():
print("-劳资是被装饰函数-")

Out[i]:

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

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

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

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

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

1.2 使用 Python 装饰器的好处

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

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

2. 装饰器初探

2.1 装饰器的基本语法

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

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

defdecorator_func(func):
defwrapper():
print("函数执行前")
func()
print("函数执行后")
returnwrapper@decorator_funcdefhello():
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 装饰器用于限制只有已登录用户才能访问某个视图函数。如果未登录用户访问被装饰的视图函数,将会被重定向到登录页面。

例如:

fromdjango.contrib.auth.decoratorsimportlogin_requiredfromdjango.shortcutsimportrender@login_requireddefmy_view(request):
# 仅允许已登录用户访问的视图函数returnrender(request, 'my_view.html')

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

3.3 @permission_required 装饰器

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

例如:

fromdjango.contrib.auth.decoratorsimportpermission_requiredfromdjango.shortcutsimportrender@permission_required('polls.can_vote')
defmy_view(request):
# 仅允许拥有 'polls.can_vote' 权限的用户访问的视图函数returnrender(request, 'my_view.html')

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

3.4 @csrf_exempt 装饰器

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

例如:

fromdjango.views.decorators.csrfimportcsrf_exemptfromdjango.httpimportHttpResponse@csrf_exemptdefmy_view(request):
# 允许未经 CSRF 保护的 POST 请求访问的视图函数ifrequest.method=='POST':
returnHttpResponse('POST request')
else:
returnHttpResponse('GET request')

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

3.5 @cache_page 装饰器

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

例如:

fromdjango.views.decorators.cacheimportcache_pagefromdjango.shortcutsimportrender@cache_page(60*10)  # 缓存 10 分钟defmy_view(request):
# 缓存整个视图函数的输出结果returnrender(request, 'my_view.html')

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

4. 函数装饰器

4.1 装饰器原理与基本语法

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

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

defdecorator_func(original_func):
defwrapper(*args, **kwargs):
# 在调用原始函数之前执行的代码result=original_func(*args, **kwargs)
# 在调用原始函数之后执行的代码returnresultreturnwrapper

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

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

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

@decorator_funcdefmy_function():
# 函数的代码逻辑

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

my_function=decorator_func(my_function)

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

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

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

例如:

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

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

@log_decoratordefadd_numbers(a, b):
returna+b

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

result=add_numbers(10, 5)

Out[]:

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

Out[]: 15

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

又如:

importtimedefperformance_decorator(original_func):
defwrapper(*args, **kwargs):
start_time=time.time()
result=original_func(*args, **kwargs)
end_time=time.time()
execution_time=end_time-start_timeprint(f"函数 {original_func.__name__} 的执行时间为:{execution_time}秒")
returnresultreturnwrapper

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

@performance_decoratordefcalculate_factorial(n):
factorial=1foriinrange(1, n+1):
factorial*=ireturnfactorial

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

result=calculate_factorial(10)

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

print(result)

Out[]:3628800

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

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

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

例如:

defrepeat(num_times):
defdecorator_func(original_func):
defwrapper(*args, **kwargs):
for_inrange(num_times):
result=original_func(*args, **kwargs)
returnresultreturnwrapperreturndecorator_func

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

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

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

Out[]:

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

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

又如:

importtimedefperformance_decorator(unit):
defdecorator_func(original_func):
defwrapper(*args, **kwargs):
start_time=time.time()
result=original_func(*args, **kwargs)
end_time=time.time()
execution_time=end_time-start_timeprint(f"函数 {original_func.__name__} 的执行时间为:{execution_time}{unit}")
returnresultreturnwrapperreturndecorator_func

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

我们可以这样使用:

@performance_decorator(unit="秒")
defcalculate_power(base, exponent):
result=base**exponenttime.sleep(1)  # 模拟函数执行的时间returnresult

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

result = calculate_power(2, 10)

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

print(result)  # 输出:1024

5. 类装饰器

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

5.1 类装饰器的结构和用法

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

例如:

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

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

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

@DecoratorClassdefgreet(name):
print(f"Hello, {name}!")

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

5.2 带状态的类装饰器

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

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

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

例如:

classCounter:
def__init__(self, original_func):
self.original_func=original_funcself.count=0def__call__(self, *args, **kwargs):
self.count+=1print(f"函数 {self.original_func.__name__} 被调用了 {self.count} 次")
result=self.original_func(*args, **kwargs)
returnresult

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

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

@Counterdefadd_numbers(a, b):
returna+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. 装饰器的组合

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

defdecorator1(original_func):
defwrapper(*args, **kwargs):
# 装饰器1的逻辑result=original_func(*args, **kwargs)
# 装饰器1的逻辑returnresultreturnwrapperdefdecorator2(original_func):
defwrapper(*args, **kwargs):
# 装饰器2的逻辑result=original_func(*args, **kwargs)
# 装饰器2的逻辑returnresultreturnwrapper

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

@decorator1@decorator2defgreet(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日志并进行多维度分析。
目录
相关文章
|
20天前
|
开发者 Python
探索Python中的装饰器:从基础到高级应用
本文将带你深入了解Python中的装饰器,这一强大而灵活的工具。我们将一起探讨装饰器的基本概念,它们如何工作,以及如何使用它们来增强函数和类的功能,同时不改变其核心逻辑。通过具体代码示例,我们将展示装饰器的创建和使用,并探索一些高级应用,比如装饰器堆栈和装饰带参数的装饰器。无论你是初学者还是有经验的开发者,这篇文章都将为你提供新的视角,帮助你更有效地使用装饰器来简化和优化你的代码。
|
21天前
|
测试技术 数据安全/隐私保护 开发者
探索Python中的装饰器:从基础到高级应用
装饰器在Python中是一个强大且令人兴奋的功能,它允许开发者在不修改原有函数代码的前提下增加额外的功能。本文将通过具体代码示例,带领读者从装饰器的基础概念入手,逐步深入到高级用法,如带参数的装饰器和装饰器嵌套等。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的见解和技巧。
|
21天前
|
开发框架 数据建模 中间件
Python中的装饰器:简化代码,增强功能
在Python的世界里,装饰器是那些静悄悄的幕后英雄。它们不张扬,却能默默地为函数或类增添强大的功能。本文将带你了解装饰器的魅力所在,从基础概念到实际应用,我们一步步揭开装饰器的神秘面纱。准备好了吗?让我们开始这段简洁而富有启发性的旅程吧!
28 6
|
9天前
|
缓存 数据安全/隐私保护 Python
python装饰器底层原理
Python装饰器是一个强大的工具,可以在不修改原始函数代码的情况下,动态地增加功能。理解装饰器的底层原理,包括函数是对象、闭包和高阶函数,可以帮助我们更好地使用和编写装饰器。无论是用于日志记录、权限验证还是缓存,装饰器都可以显著提高代码的可维护性和复用性。
21 5
|
23天前
|
测试技术 Python
探索Python中的装饰器:简化代码,增强功能
在Python的世界中,装饰器是那些能够为我们的代码增添魔力的小精灵。它们不仅让代码看起来更加优雅,还能在不改变原有函数定义的情况下,增加额外的功能。本文将通过生动的例子和易于理解的语言,带你领略装饰器的奥秘,从基础概念到实际应用,一起开启Python装饰器的奇妙旅程。
35 11
|
20天前
|
测试技术 开发者 Python
探索Python中的装饰器:从入门到实践
装饰器,在Python中是一块强大的语法糖,它允许我们在不修改原函数代码的情况下增加额外的功能。本文将通过简单易懂的语言和实例,带你一步步了解装饰器的基本概念、使用方法以及如何自定义装饰器。我们还将探讨装饰器在实战中的应用,让你能够在实际编程中灵活运用这一技术。
37 7
|
19天前
|
Python
探索Python中的装饰器:简化代码,增强功能
在Python的世界里,装饰器就像是给函数穿上了一件神奇的外套,让它们拥有了超能力。本文将通过浅显易懂的语言和生动的比喻,带你了解装饰器的基本概念、使用方法以及它们如何让你的代码变得更加简洁高效。让我们一起揭开装饰器的神秘面纱,看看它是如何在不改变函数核心逻辑的情况下,为函数增添新功能的吧!
|
20天前
|
程序员 测试技术 数据安全/隐私保护
深入理解Python装饰器:提升代码重用与可读性
本文旨在为中高级Python开发者提供一份关于装饰器的深度解析。通过探讨装饰器的基本原理、类型以及在实际项目中的应用案例,帮助读者更好地理解并运用这一强大的语言特性。不同于常规摘要,本文将以一个实际的软件开发场景引入,逐步揭示装饰器如何优化代码结构,提高开发效率和代码质量。
44 6
|
19天前
|
存储 缓存 Python
Python中的装饰器深度解析与实践
在Python的世界里,装饰器如同一位神秘的魔法师,它拥有改变函数行为的能力。本文将揭开装饰器的神秘面纱,通过直观的代码示例,引导你理解其工作原理,并掌握如何在实际项目中灵活运用这一强大的工具。从基础到进阶,我们将一起探索装饰器的魅力所在。
|
20天前
|
测试技术 开发者 Python
深入理解Python装饰器:从基础到高级应用
本文旨在为读者提供一个全面的Python装饰器指南,从其基本概念讲起,逐步深入探讨其高级应用。我们将通过实例解析装饰器的工作原理,并展示如何利用它们来增强函数功能、控制程序流程以及实现代码的模块化。无论你是Python初学者还是经验丰富的开发者,本文都将为你提供宝贵的见解和实用的技巧,帮助你更好地掌握这一强大的语言特性。
30 4