nonlocal 关键字,那是通往今天内容的基石。今天我们要解锁 Python 中最优雅、最强大的特性之一——装饰器(Decorator)。装饰器(Decorator) 是一个用于增强函数功能的工具。它允许你在不修改原函数代码的前提下,为函数添加额外的逻辑(如日志、计时、权限验证等)。
装饰器本质上是一个高阶函数:它接收一个函数作为参数,并返回一个新的函数。
def my_func():print("Hello")def wrapper():print("Before")my_func()print("After")wrapper() # 调用 wrapper 而不是 my_func
def my_decorator(func):def wrapper():print("Before")func()print("After")return wrapper@my_decorator # 等价于 my_func = my_decorator(my_func)def my_func():print("Hello")my_func() # 直接调用原函数名,但执行了增强逻辑
@my_decorator 放在函数定义上方,Python 会自动执行 my_func = my_decorator(my_func)。上面的例子只能装饰无参函数。实际工作中,函数通常有参数和返回值。
import functoolsdef decorator(func):@functools.wraps(func) # 保留原函数的元数据(__name__, __doc__)def wrapper(*args, **kwargs):# 1. 前置逻辑print("Before execution")# 2. 执行原函数result = func(*args, **kwargs)# 3. 后置逻辑print("After execution")# 4. 返回结果return resultreturn wrapper
# 不使用 functools.wrapsdef bad_decorator(func):def wrapper(*args, **kwargs):return func(*args, **kwargs)return wrapper@bad_decoratordef hello():"""This is hello function"""passprint(hello.__name__) # 输出:wrapper (❌ 原函数名丢失)# 使用 functools.wrapsdef good_decorator(func):@functools.wraps(func)def wrapper(*args, **kwargs):return func(*args, **kwargs)return wrapper@good_decoratordef hello():"""This is hello function"""passprint(hello.__name__) # 输出:hello (✅ 正确)
有时我们需要控制装饰器的行为,例如 @retry(times=3)。这需要三层嵌套。
times)。func)。*args)。import functoolsdef repeat(times):def decorator(func):@functools.wraps(func)def wrapper(*args, **kwargs):for _ in range(times):result = func(*args, **kwargs)return resultreturn wrapperreturn decorator@repeat(times=3)def say_hi():print("Hi!")say_hi()# 输出:# Hi!# Hi!# Hi!
import functoolsimport logginglogging.basicConfig(level=logging.INFO)def log_func(func):@functools.wraps(func)def wrapper(*args, **kwargs):logging.info(f"Calling {func.__name__} with {args}, {kwargs}")result = func(*args, **kwargs)logging.info(f"{func.__name__} returned {result}")return resultreturn wrapper@log_funcdef add(a, b):return a + badd(1, 2)
import timeimport functoolsdef timer(func):@functools.wraps(func)def wrapper(*args, **kwargs):start = time.time()result = func(*args, **kwargs)end = time.time()print(f"{func.__name__} 耗时:{end - start:.4f}s")return resultreturn wrapper@timerdef slow_task():time.sleep(1)slow_task()
def require_login(func):@functools.wraps(func)def wrapper(user, *args, **kwargs):if not user.get("is_logged_in"):return "Error: 请先登录"return func(user, *args, **kwargs)return wrapper@require_logindef view_profile(user):return f"欢迎 {user['name']}"user = {"name": "Alice", "is_logged_in": False}print(view_profile(user)) # Error: 请先登录
import timedef retry(max_attempts=3, delay=1):def decorator(func):@functools.wraps(func)def wrapper(*args, **kwargs):for i in range(max_attempts):try:return func(*args, **kwargs)except Exception as e:if i == max_attempts - 1:raise eprint(f"失败,{delay}秒后重试...")time.sleep(delay)return wrapperreturn decorator@retry(max_attempts=3, delay=1)def connect_db():import randomif random.random() < 0.7:raise ConnectionError("连接失败")return "连接成功"# 运行多次直到成功或耗尽次数try:print(connect_db())except Exception as e:print(f"最终失败:{e}")
当一个函数被多个装饰器修饰时,顺序很重要。
def decorator_a(func):def wrapper():print("A 前")func()print("A 后")return wrapperdef decorator_b(func):def wrapper():print("B 前")func()print("B 后")return wrapper@decorator_a@decorator_bdef say_hello():print("Hello")say_hello()
A 前B 前HelloB 后A 后
💡 记忆法则:
@b 后 @a)。A 前 后 B 前)。say_hello = decorator_a(decorator_b(say_hello))。装饰器不仅可以是函数,也可以是类(实现 __call__ 方法)。
class CountCalls:def __init__(self, func):self.func = funcself.count = 0def __call__(self, *args, **kwargs):self.count += 1print(f"调用次数:{self.count}")return self.func(*args, **kwargs)@CountCallsdef say_hello():print("Hello")say_hello() # 调用次数:1say_hello() # 调用次数:2
@functools.wraps:保留原函数信息,方便调试和文档生成。*args, **kwargs:确保装饰器通用,不限制原函数参数。@login_required, @cache)。编写一个 @cache 装饰器,缓存函数的返回值。如果相同的参数再次传入,直接返回缓存结果,不再执行函数。
参考
import functoolsdef cache(func):cached_results = {}@functools.wraps(func)def wrapper(*args):if args in cached_results:print("命中缓存")return cached_results[args]result = func(*args)cached_results[args] = resultreturn resultreturn wrapper@cachedef expensive_calc(x):print("计算中...")return x * xprint(expensive_calc(5)) # 计算中... 25print(expensive_calc(5)) # 命中缓存 25
编写一个 @require_level(level) 装饰器,只有当用户的 level 大于等于指定值时才能执行函数。
参考
import functoolsdef require_level(min_level):def decorator(func):@functools.wraps(func)def wrapper(user, *args, **kwargs):if user.get("level", 0) < min_level:return f"权限不足:需要等级 {min_level}"return func(user, *args, **kwargs)return wrapperreturn decorator@require_level(5)def admin_panel(user):return "欢迎进入管理面板"user = {"name": "Bob", "level": 3}print(admin_panel(user)) # 权限不足:需要等级 5
概念 | 说明 |
|---|---|
本质 | 高阶函数(接收函数,返回函数) |
语法糖 |
|
通用模板 | 使用 |
带参装饰器 | 需要三层嵌套(参数 -> 函数 -> 参数) |
执行顺序 | 多个装饰器时,从下往上定义,从外往内执行 |
应用场景 | 日志、计时、鉴权、缓存、重试 |
