Python装饰器原理全解析
时间:2025-09-30 16:38:37 323浏览 收藏
Python装饰器是一种强大的语言特性,它允许在不修改原函数代码的基础上,为函数添加额外的功能,例如日志记录、性能测试和权限验证等。装饰器本质上是一个函数,它接收一个函数作为参数,并返回一个新的函数。通过`@`语法糖,可以将装饰器应用到函数上,实现功能的增强。本文将深入解析Python装饰器的工作原理,包括如何处理带参数的函数,以及函数装饰器与类装饰器的区别。同时,还将探讨如何编写带参数的装饰器,以及如何使用`functools.wraps`来保留原始函数的元数据,避免影响函数信息。此外,文章还将介绍装饰器链的使用方法,展示多个装饰器叠加使用时的执行顺序,助你全面掌握Python装饰器的应用。
装饰器通过函数作为第一类对象实现,定义一个接收函数的装饰器,在其内部定义wrapper函数并添加额外逻辑,最后返回wrapper;使用@语法糖将原函数替换为包装后的函数,从而在不修改原函数代码的情况下增强功能。

装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外的功能,装饰器的返回值也是一个函数对象。 它们经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理等等。
装饰器就像给函数穿上了一件“外衣”,这件“外衣”可以给函数增加额外的能力,但不会改变函数本身。
装饰器是如何实现的?
Python的装饰器是基于函数是“第一类对象”这一概念实现的。这意味着函数可以像任何其他对象一样被传递、赋值和作为返回值。
简单来说,装饰器通过以下步骤工作:
- 定义一个装饰器函数,该函数接收一个函数作为参数。
- 在装饰器函数内部,定义一个新的函数(通常称为wrapper函数)。
- wrapper函数中,调用原始函数,并在调用前后添加额外的功能。
- 装饰器函数返回wrapper函数。
当使用@decorator语法糖来装饰一个函数时,实际上是将原始函数作为参数传递给装饰器函数,并将装饰器返回的wrapper函数赋值给原始函数的名字。
def my_decorator(func):
def wrapper():
print("Something is happening before the function is called.")
func()
print("Something is happening after the function is called.")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello()这段代码的执行结果是:
Something is happening before the function is called. Hello! Something is happening after the function is called.
如何处理带参数的函数?
如果被装饰的函数带有参数,wrapper函数需要能够接收和传递这些参数。可以使用*args和**kwargs来实现:
def my_decorator(func):
def wrapper(*args, **kwargs):
print("Before calling the function")
result = func(*args, **kwargs)
print("After calling the function")
return result
return wrapper
@my_decorator
def add(a, b):
return a + b
print(add(2, 3))输出结果:
Before calling the function After calling the function 5
装饰器能做什么?除了日志和性能监控
装饰器可以做的远不止日志和性能监控。 它们可以用于:
- 权限验证: 检查用户是否有权限访问某个函数。
- 缓存: 缓存函数的返回值,避免重复计算。
- 重试机制: 在函数执行失败时自动重试。
- 类型检查: 验证函数参数的类型。
- 信号处理: 在函数执行前后发送信号。
- 路由控制: 在Web框架中,将URL映射到对应的处理函数。
例如,一个简单的缓存装饰器:
import functools
def cache(func):
cached_values = {}
@functools.wraps(func)
def wrapper(*args):
if args in cached_values:
return cached_values[args]
else:
result = func(*args)
cached_values[args] = result
return result
return wrapper
@cache
def expensive_operation(n):
print(f"Calculating for {n}...")
return n * n
print(expensive_operation(5))
print(expensive_operation(5)) # 从缓存中获取装饰器与类装饰器有什么区别?
除了函数装饰器,Python还支持类装饰器。 类装饰器接收一个函数或类作为参数,并返回一个修改后的函数或类。
类装饰器通常通过实现__call__方法来工作。 当使用类装饰器装饰一个函数时,实际上是创建了该类的一个实例,并将被装饰的函数作为参数传递给类的构造函数。 当调用被装饰的函数时,实际上是调用了类实例的__call__方法。
class MyDecorator:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print("Before calling the function")
result = self.func(*args, **kwargs)
print("After calling the function")
return result
@MyDecorator
def say_hello(name):
print(f"Hello, {name}!")
say_hello("Alice")如何编写一个带参数的装饰器?
有时候,我们需要一个可以接收参数的装饰器。 这可以通过创建一个返回装饰器函数的函数来实现。
def repeat(num_times):
def decorator_repeat(func):
def wrapper(*args, **kwargs):
for _ in range(num_times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator_repeat
@repeat(num_times=3)
def greet(name):
print(f"Hello, {name}!")
greet("Bob")这个例子中,repeat函数接收一个num_times参数,并返回一个装饰器函数decorator_repeat。 decorator_repeat函数再接收被装饰的函数func,并返回wrapper函数。
装饰器会影响函数的元数据吗?
默认情况下,装饰器会改变被装饰函数的元数据,例如__name__和__doc__。 这可能会导致一些问题,例如在使用help()函数时显示不正确的信息。
为了解决这个问题,可以使用functools.wraps装饰器来保留原始函数的元数据。
import functools
def my_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
"""This is the wrapper function."""
print("Before calling the function")
result = func(*args, **kwargs)
print("After calling the function")
return result
return wrapper
@my_decorator
def say_hello():
"""This is the original function."""
print("Hello!")
print(say_hello.__name__)
print(say_hello.__doc__)使用了functools.wraps后,say_hello.__name__会输出say_hello,say_hello.__doc__会输出This is the original function.。 如果没有使用functools.wraps,则会输出wrapper函数的信息。
装饰器链:多个装饰器叠加使用
可以同时使用多个装饰器来增强一个函数的功能,这就是装饰器链。 装饰器的应用顺序是从下往上,从里到外。
def bold(func):
def wrapper(*args, **kwargs):
return "<b>" + func(*args, **kwargs) + "</b>"
return wrapper
def italic(func):
def wrapper(*args, **kwargs):
return "<i>" + func(*args, **kwargs) + "</i>"
return wrapper
@bold
@italic
def get_message(message):
return message
print(get_message("Hello"))在这个例子中,get_message函数首先被italic装饰器装饰,然后再被bold装饰器装饰。 因此,输出结果是Hello。
今天带大家了解了的相关知识,希望对你有所帮助;关于文章的技术知识我们会一点点深入介绍,欢迎大家关注golang学习网公众号,一起学习编程~
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
283 收藏
-
349 收藏
-
291 收藏
-
204 收藏
-
401 收藏
-
227 收藏
-
400 收藏
-
327 收藏
-
124 收藏
-
450 收藏
-
347 收藏
-
464 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习