一、什么是装饰器?
装饰器(Decorator) 是 Python 中的一种设计模式,它允许在不修改原函数代码的情况下,给函数添加额外的功能。装饰器本质上是一个函数,它接收一个函数作为参数,并返回一个新的函数(增强后的版本)。
deftimer(func):
"""计算函数执行时间的装饰器"""
defwrapper(*args, **kwargs):
import time
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} 耗时: {end - start:.4f}秒")
return result
return wrapper
@timer
defslow_function():
import time
time.sleep(1)
return"完成"
print(slow_function()) # slow_function 耗时: 1.0002秒 \n 完成
二、装饰器的本质:语法糖
@decorator 语法实际上是高阶函数的语法糖,它将目标函数作为参数传递给装饰器函数,并将返回的新函数替换原函数。
# 使用 @ 语法
@timer
defmy_func():
pass
# 等价于
defmy_func():
pass
my_func = timer(my_func)
三、装饰器的核心原理
3.1 函数在 Python 中是“一等公民”
Python 中函数可以:
defgreet(name):
returnf"Hello, {name}"
# 赋值给变量
say_hello = greet
print(say_hello("张三")) # Hello, 张三
# 作为参数传递
defcall_func(func, arg):
return func(arg)
print(call_func(greet, "李四")) # Hello, 李四
# 作为返回值
defmake_greeter(prefix):
defgreeter(name):
returnf"{prefix}, {name}"
return greeter
3.2 装饰器的基本结构
defdecorator(func):
"""装饰器函数"""
defwrapper(*args, **kwargs):
# 调用前可以添加额外操作
print("调用函数前")
result = func(*args, **kwargs)
# 调用后可以添加额外操作
print("调用函数后")
return result
return wrapper
四、装饰器的执行流程
deflog(func):
print(f"装饰器函数被调用,正在装饰 {func.__name__}")
defwrapper(*args, **kwargs):
print(f"wrapper 函数被调用")
return func(*args, **kwargs)
return wrapper
@log
defsay_hello(name):
print(f"Hello, {name}")
print("--- 执行函数 ---")
say_hello("张三")
输出:
装饰器函数被调用,正在装饰 say_hello
--- 执行函数 ---
wrapper 函数被调用
Hello, 张三
执行步骤:
- 1. Python 解释器执行到
@log 时,立即调用 log(say_hello)。 - 2.
log 函数执行,定义并返回 wrapper 函数。 - 3. 原函数
say_hello 被替换为 wrapper。 - 4. 当调用
say_hello("张三") 时,实际执行的是 wrapper 函数。
五、带参数的装饰器
装饰器本身也可以接收参数,这需要三层嵌套的函数结构。
defrepeat(times):
"""带参数的装饰器:重复执行指定次数"""
defdecorator(func):
defwrapper(*args, **kwargs):
for _ inrange(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
defgreet(name):
print(f"Hello, {name}")
greet("张三")
# 输出三次 "Hello, 张三"
执行流程:
- 1.
@repeat(3) 首先调用 repeat(3),返回 decorator 函数。 - 2.
@decorator 将 greet 函数传递给 decorator,返回 wrapper。
六、装饰器保留函数元信息
使用 functools.wraps 可以保留原函数的名称、文档字符串等元信息。
deftimer(func):
"""计时装饰器"""
defwrapper(*args, **kwargs):
"""wrapper 的文档"""
import time
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} 耗时: {end - start:.4f}秒")
return result
return wrapper
@timer
defmy_func():
"""my_func 的文档"""
pass
print(my_func.__name__) # wrapper(不是 my_func)
print(my_func.__doc__) # wrapper 的文档(不是 my_func 的文档)
使用 @wraps 修复:
from functools import wraps
deftimer(func):
@wraps(func) # 保留原函数元信息
defwrapper(*args, **kwargs):
import time
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} 耗时: {end - start:.4f}秒")
return result
return wrapper
@timer
defmy_func():
"""my_func 的文档"""
pass
print(my_func.__name__) # my_func
print(my_func.__doc__) # my_func 的文档
七、类装饰器
除了函数,类也可以作为装饰器,需要实现 __call__ 方法。
classTimer:
def__init__(self, func):
self.func = func
self.count = 0
def__call__(self, *args, **kwargs):
import time
start = time.time()
result = self.func(*args, **kwargs)
end = time.time()
self.count += 1
print(f"{self.func.__name__} 第 {self.count} 次调用,耗时: {end - start:.4f}秒")
return result
@Timer
defslow_function():
import time
time.sleep(1)
return"完成"
slow_function()
slow_function()
八、多个装饰器叠加
一个函数可以应用多个装饰器,执行顺序是从下往上。
defdecorator_a(func):
print("装饰器 A 被调用")
defwrapper(*args, **kwargs):
print("A 开始")
result = func(*args, **kwargs)
print("A 结束")
return result
return wrapper
defdecorator_b(func):
print("装饰器 B 被调用")
defwrapper(*args, **kwargs):
print("B 开始")
result = func(*args, **kwargs)
print("B 结束")
return result
return wrapper
@decorator_a
@decorator_b
defmy_func():
print("执行原函数")
my_func()
输出:
装饰器 B 被调用
装饰器 A 被调用
A 开始
B 开始
执行原函数
B 结束
A 结束
等价于:
my_func = decorator_a(decorator_b(my_func))
九、实战案例
9.1 权限验证装饰器
from functools import wraps
defrequire_permission(permission):
"""权限验证装饰器"""
defdecorator(func):
@wraps(func)
defwrapper(*args, **kwargs):
# 模拟从上下文获取当前用户权限
current_user = getattr(wrapper, 'user', None)
if current_user and permission in current_user.get('permissions', []):
return func(*args, **kwargs)
else:
raise PermissionError(f"缺少权限: {permission}")
return wrapper
return decorator
classUser:
def__init__(self, name, permissions):
self.name = name
self.permissions = permissions
defset_current_user(user):
defdecorator(func):
@wraps(func)
defwrapper(*args, **kwargs):
wrapper.user = user
return func(*args, **kwargs)
return wrapper
return decorator
@set_current_user(User("张三", ["read", "write"]))
@require_permission("write")
defdelete_file(filename):
print(f"删除文件: {filename}")
delete_file("test.txt") # 删除文件: test.txt
9.2 重试装饰器
from functools import wraps
import time
defretry(max_attempts=3, delay=1, exceptions=(Exception,)):
"""重试装饰器"""
defdecorator(func):
@wraps(func)
defwrapper(*args, **kwargs):
for attempt inrange(1, max_attempts + 1):
try:
return func(*args, **kwargs)
except exceptions as e:
print(f"第 {attempt} 次尝试失败: {e}")
if attempt == max_attempts:
raise
time.sleep(delay)
returnNone
return wrapper
return decorator
@retry(max_attempts=5, delay=0.5, exceptions=(ValueError,))
defunstable_function():
import random
if random.random() < 0.7:
raise ValueError("随机失败")
return"成功"
print(unstable_function())
9.3 缓存装饰器
from functools import wraps
defcache(maxsize=128):
"""简单缓存装饰器"""
defdecorator(func):
cache_dict = {}
@wraps(func)
defwrapper(*args):
if args in cache_dict:
print(f"缓存命中: {args}")
return cache_dict[args]
print(f"计算: {args}")
result = func(*args)
# 简单的 LRU 淘汰
iflen(cache_dict) >= maxsize:
cache_dict.pop(next(iter(cache_dict)))
cache_dict[args] = result
return result
return wrapper
return decorator
@cache(maxsize=3)
deffibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(10))
十、注意事项
- 1. 装饰器的执行时机:装饰器在函数定义时立即执行(模块加载时),而不是在函数调用时。
- 2. 保留元信息:始终使用
@functools.wraps 保留原函数的名称、文档字符串等。 - 3. 函数签名:装饰器可能会改变函数的参数签名,影响内省和文档生成工具。
- 4. 性能影响:装饰器会增加函数调用开销,对性能敏感的函数需注意。
- 5. 调试困难:多层装饰器嵌套会使调试变得复杂,可以使用
functools.wraps 改善。
十一、总结
- • 装饰器 是一种不修改原函数代码就能添加功能的机制。
- • 本质:装饰器是返回函数的高阶函数,
@decorator 是语法糖。 - • 类装饰器 通过实现
__call__ 方法实现。 - • 最佳实践:使用
functools.wraps 保留函数元信息。
装饰器是 Python 中最优雅、最强大的特性之一。理解其原理,可以帮助你写出更简洁、可复用的代码,也是学习框架源码的必备知识。