装饰器是Python中最强大也最容易让人困惑的特性之一。第一次接触装饰器时,我也曾被那些嵌套的函数和@符号搞得晕头转向。但当我真正理解它的工作原理后,发现这简直是Python最优雅的设计之一。
简单来说,装饰器就是一个"函数包装器" - 它能在不碰原函数代码的情况下,给函数添加新功能。想象你有一部手机(原函数),装饰器就像手机壳,既保护了手机又增加了新特性,而且随时可以更换不同的壳。
装饰器的本质是闭包,要理解装饰器必须先掌握闭包。闭包是指在一个内部函数中引用了外部函数的变量,并且外部函数返回内部函数。这种结构可以"记住"创建时的环境。
python复制def outer(msg):
def inner():
print(msg)
return inner
my_func = outer("Hello")
my_func() # 输出"Hello"
在这个例子中,inner函数记住了msg参数的值,即使outer函数已经执行完毕。这种"记忆"能力正是装饰器能够工作的关键。
一个最简单的装饰器模板长这样:
python复制def decorator(func):
def wrapper(*args, **kwargs):
# 在这里添加新功能
result = func(*args, **kwargs) # 调用原函数
# 也可以在这里添加功能
return result
return wrapper
这个结构有三个关键点:
让我们实现一个实用的函数计时装饰器:
python复制import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
end = time.perf_counter()
print(f"{func.__name__} 执行耗时: {end-start:.4f}秒")
return result
return wrapper
@timer
def long_running_func(n):
return sum(i*i for i in range(n))
long_running_func(1000000)
这个装饰器可以精确测量任何函数的执行时间,而且完全不影响原函数的功能。
有时我们需要装饰器本身也能接收参数。这时需要再加一层嵌套:
python复制def repeat(num_times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(num_times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(num_times=3)
def greet(name):
print(f"Hello {name}")
greet("World")
这个@repeat(3)会让函数自动执行3次。多层嵌套看起来复杂,但遵循固定模式:最外层接收装饰器参数,中间层接收函数,最内层实现包装逻辑。
使用装饰器后,原函数的__name__、__doc__等元信息会被覆盖。可以使用functools.wraps来保留:
python复制from functools import wraps
def logged(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"调用函数: {func.__name__}")
return func(*args, **kwargs)
return wrapper
Python允许堆叠多个装饰器,执行顺序是从下往上:
python复制@decorator1
@decorator2
def my_func():
pass
# 等价于 my_func = decorator1(decorator2(my_func))
除了函数,装饰器也可以是基于类的:
python复制class CountCalls:
def __init__(self, func):
self.func = func
self.num_calls = 0
def __call__(self, *args, **kwargs):
self.num_calls += 1
print(f"调用次数: {self.num_calls}")
return self.func(*args, **kwargs)
@CountCalls
def say_hello():
print("Hello")
say_hello()
say_hello()
python复制def monitor_performance(func):
@wraps(func)
def wrapper(*args, **kwargs):
start_mem = memory_profiler.memory_usage()[0]
start_time = time.perf_counter()
result = func(*args, **kwargs)
end_time = time.perf_counter()
end_mem = memory_profiler.memory_usage()[0]
print(f"内存使用: {end_mem - start_mem:.2f} MB")
print(f"执行时间: {end_time - start_time:.4f} 秒")
return result
return wrapper
python复制def requires_auth(role="user"):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
if not current_user.is_authenticated:
raise PermissionError("需要登录")
if role == "admin" and not current_user.is_admin:
raise PermissionError("需要管理员权限")
return func(*args, **kwargs)
return wrapper
return decorator
@requires_auth(role="admin")
def delete_user(user_id):
# 管理员删除用户逻辑
pass
python复制from functools import lru_cache
@lru_cache(maxsize=128)
def expensive_calculation(n):
print(f"计算 {n}...")
return n * n
# 第一次调用会真正计算
expensive_calculation(5)
# 第二次调用直接返回缓存结果
expensive_calculation(5)
一个常见的误解是装饰器在被装饰函数调用时执行。实际上,装饰器在模块导入时就会立即执行:
python复制def decorator(func):
print("装饰器执行了!")
def wrapper():
print("wrapper执行了")
func()
return wrapper
@decorator
def my_func():
print("原函数执行了")
# 仅仅导入模块就会打印"装饰器执行了!"
# 调用my_func()才会打印另外两条
当装饰器出现问题时,可以:
python复制def debug_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"调用 {func.__name__},参数: {args}, {kwargs}")
return func(*args, **kwargs)
return wrapper
在类中使用装饰器时,注意@staticmethod和@classmethod的顺序:
python复制class MyClass:
@decorator
@staticmethod
def my_method():
pass
# 顺序很重要!@staticmethod必须是最内层的装饰器
虽然装饰器很强大,但也要注意:
一个实用的建议是,为项目创建decorators.py集中存放常用装饰器,方便管理和复用。
装饰器是Python高级编程的里程碑,掌握它能让你写出更简洁、更强大的代码。从简单的日志记录到复杂的权限系统,装饰器都能优雅地解决问题。我建议从简单的装饰器开始练习,逐步构建更复杂的应用。