在Python的世界里,装饰器(Decorator)是最优雅、最Pythonic的特性之一,也是无数初学者眼中的"拦路虎"。初次接触时,语法怪异的@符号、嵌套函数的层层包裹、闭包概念的似懂非懂——让人不禁疑惑:明明可以直接调函数,为什么非要绕这么大一圈?
但当你真正理解装饰器之后,你会发现它是代码复用的终极武器。从日志记录、权限校验、性能计时,到缓存、重试、限流,装饰器都能以极简的方式为函数"增强能力",而无需修改函数本身。
装饰器的本质很简单:它是一个接收函数、返回增强后函数的高阶函数。下面我们从基础到进阶,一步步拆解。
一、基础篇:理解闭包是理解装饰器的前提
装饰器的底层依赖于闭包(Closure)。闭包的核心是:函数可以记住定义时所在环境中的变量,即使该环境已经执行完毕。
def outer(x): def inner(y): return x + y # inner 记住了 outer 的参数 x return inner add_5 = outer(5) print(add_5(3)) # 输出 8 —— 5 被记住了
inner就是一个闭包,它"捕获"了outer作用域中的x。装饰器正是利用闭包,在函数定义时"捕获"被装饰的函数,并返回一个增强后的新函数。
二、入门篇:手写一个最简单的装饰器
一个标准的装饰器长这样:
def timer(func): import time def wrapper(args, kwargs): start = time.time() result = func(args, kwargs) end = time.time() print(f"{func.__name__} 执行耗时: {end start:.4f}s") return result return wrapper @timer # 语法糖,等价于 slow_function = timer(slow_function) def slow_function(): time.sleep(1) return "done" slow_function() # 输出: slow_function 执行耗时: 1.0012s@timer这个语法糖,本质上就是把slow_function作为参数传给timer,然后用返回的wrapper替换原函数。一切都很优雅,但有一个小问题:
print(slow_function.__name__) # 输出: wrapper —— 函数名字被改了!
原函数的元信息(名字、文档字符串)被覆盖了。解决这个问题非常简单——用functools.wraps:
from functools import wraps def timer(func): @wraps(func) # 保留 func 的元信息 def wrapper(args, kwargs): # ... 同上面 return wrapper
记住:写装饰器永远带上@wraps,这是一个好习惯。
三、进阶篇:带参数的装饰器
有时候,我们想让装饰器接受参数,比如指定日志级别或重试次数:
def retry(max_attempts=3): def decorator(func): @wraps(func) def wrapper(args, kwargs): for attempt in range(1, max_attempts + 1): try: return func(args, kwargs) except Exception as e: if attempt == max_attempts: raise print(f"第 {attempt} 次重试失败: {e}") return None return wrapper return decorator @retry(max_attempts=5) # 带参数调用 def unstable_network_call(): # 可能失败的操作 pass注意这里的三层嵌套:retry接收参数,返回一个真正的装饰器decorator;decorator接收函数,返回wrapper。理解这个结构,带参数的装饰器就不再神秘了。
四、进阶篇:类装饰器
除了用函数写装饰器,还可以用类来实现。类的__call__方法让实例可以像函数一样被调用:
class CountCalls: def __init__(self, func): self.func = func self.count = 0 def __call__(self, args, kwargs): self.count += 1 print(f"{self.func.__name__} 已被调用 {self.count} 次") return self.func(args, kwargs) @CountCalls def say_hello(): print("Hello!") say_hello() # say_hello 已被调用 1 次 say_hello() # say_hello 已被调用 2 次类装饰器的优势在于可以维护状态(如调用次数),比闭包更直观。
五、实战场景:装饰器的经典用途
1. 权限校验
def require_login(func): @wraps(func) def wrapper(request, args, kwargs): if not request.user.is_authenticated: raise PermissionError("请先登录") return func(request, args, kwargs) return wrapper @require_login def profile_page(request): return "用户资料"2. 缓存计算结果(LRU)
from functools import lru_cache @lru_cache(maxsize=128) def fibonacci(n): if n < 2: return n return fibonacci(n1) + fibonacci(n2)
functools.lru_cache是Python内置的缓存装饰器,能自动缓存函数的计算结果,对递归函数效率提升巨大。Fibonacci第40项递归调用次数从3亿次降至40次——这就是装饰器的力量。
3. 多个装饰器叠加
@login_required @log_execution @timer def payment_process(): # 先 timer -> 再 log -> 最后 login pass
多个装饰器叠加时,执行顺序是从下往上——离函数最近的最先执行。
写在最后
装饰器是Python将函数式编程思想融入日常开发的完美体现。它的核心价值在于关注点分离——业务逻辑只关心"做什么",横切关注点(日志、缓存、权限)交给装饰器处理。
从最简单的@timer到复杂的带参数装饰器,从函数装饰器到类装饰器,掌握装饰器意味着你对Python的函数模型、闭包机制、元编程有了更深的理解。
当你下一次发现自己在多个函数里重复写着同样的try-except、同样的日志记录代码时,停下来想一想:这里是不是应该有一个装饰器?