上次我们聊了闭包,说它是“内层函数+外层变量”的组合。今天要讲的装饰器,就是闭包最闪亮的应用——它能让你在不修改原函数的情况下,给函数统一加上日志、计时、校验、缓存等功能。读完你会:
闭包是怎么“升级”成装饰器的
*args、**kwargs 在装饰器里怎么用
带参数的装饰器怎么写
多个装饰器同时用的顺序问题
类装饰器又是什么
用几个真实场景玩转装饰器
一、什么是函数装饰器?
用一句话定义:
装饰器是一个可调用对象(通常是函数),它接收一个函数作为参数,并返回一个新函数。
装饰器的核心作用,就是在不修改原函数代码的前提下,增强或改变原函数的功能。
这背后依赖的就是闭包:外层的装饰器函数捕获了被装饰的函数,返回一个内层的 wrapper 函数,以后调用原函数,其实都是在调用 wrapper。
def my_decorator(func): # 1. 接收被装饰的函数 def wrapper(*args, **kwargs): # 3. 用 *args 和 **kwargs 接收任意参数 print("前置增强逻辑...") result = func(*args, **kwargs) # 4. 调用原函数 print("后置增强逻辑...") return result # 4. 记得把原函数的返回值 return 出去 return wrapper # 2. 返回新函数(wrapper)@my_decoratordef greet(name): return f"Hello, {name}!"print(greet("Alice"))
@my_decorator 等价于 greet = my_decorator(greet)。从此 greet 这个变量名,指向的是 wrapper 函数,但原来的功能一点儿没少,还被“包裹”了一层。接收被装饰函数,返回新函数(wrapper) —— 装饰器本质就是一个“函数工厂”。
装饰器“吐出来”的是 wrapper,以后别人调用的也是 wrapper。
用 *args, **kwargs 保证参数兼容性 —— 你不知道原函数需要几个参数,用万能参数接收准没错。
wrapper 里要调用原函数,并把返回值 return 出去 —— 否则原函数的返回值就丢了。
缺了任何一点,装饰器就可能出 bug。
二、带参数的装饰器
有时候我们希望装饰器能接收一些配置参数,比如“这个日志要输出到哪个文件”。这时候需要在普通装饰器外面再包一层函数。
def log_to_file(filename): # 最外层负责接收参数,并返回真正的装饰器 def decorator(func): def wrapper(*args, **kwargs): result = func(*args, **kwargs) with open(filename, 'a') as f: f.write(f"{func.__name__} 被调用,结果:{result}\n") return result return wrapper return decorator@log_to_file("app.log")def add(a, b): return a + bprint(add(3, 4)) # 同时会在 app.log 里写入一条记录
原理:@log_to_file("app.log") 相当于 add = log_to_file("app.log")(add)。最外层接收 "app.log",返回 decorator,然后 decorator(add) 再返回最终的 wrapper。三层函数,逻辑依然清晰。
三、多个装饰器一起用,顺序怎么记
@decorator_a@decorator_bdef my_func(): pass
执行与调用顺序:
my_func = decorator_a(decorator_b(my_func))
所以靠近函数定义的 decorator_b 会先执行装饰动作,decorator_a 后执行。
def dec_a(func): print("装饰器 A 执行") def wrapper(*args, **kwargs): print("进入 wrapper A") result = func(*args, **kwargs) print("离开 wrapper A") return result return wrapperdef dec_b(func): print("装饰器 B 执行") def wrapper(*args, **kwargs): print("进入 wrapper B") result = func(*args, **kwargs) print("离开 wrapper B") return result return wrapper@dec_a@dec_bdef say(): print("原函数执行中...")say()
记住口诀:装饰时从下往上,调用时从上往下(先外后内)。如果你觉得函数套函数有点绕,或者装饰器本身需要维护状态,可以用类装饰器。
只要一个类实现了 __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)@CountCallsdef hello(): print("你好!")hello()hello()
原理解析:
@CountCalls 相当于 hello = CountCalls(hello),此时 hello 变成了一个 CountCalls 的实例。调用 hello() 时,会自动触发 __call__ 方法,方法里再调用原函数。
类装饰器特别适合需要记录状态(比如计数器、缓存)的场景。
五、实战:四行装饰器搞定日志、计时、校验、缓存
## 通用日志装饰器def logger(func): def wrapper(*args, **kwargs): print(f"[LOG] 调用 {func.__name__}({args}, {kwargs})") return func(*args, **kwargs) return wrapper
## 性能计时装饰器import timedef timer(func): def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) print(f"[TIMER] {func.__name__} 耗时 {time.time()-start:.4f} 秒") return result return wrapper
## 参数校验装饰器def check_positive(func): def wrapper(*args, **kwargs): for arg in args: if isinstance(arg, (int, float)) and arg < 0: raise ValueError("参数不能为负数") return func(*args, **kwargs) return wrapper
## 简易缓存装饰器def cache(func): memo = {} def wrapper(*args): if args not in memo: memo[args] = func(*args) return memo[args] return wrapper@cachedef fib(n): if n <= 1: return n return fib(n-1) + fib(n-2)print(fib(30)) # 瞬间出结果
装饰器 = 闭包的经典应用,用一个可调用对象包裹另一个函数,增强其行为。
基础结构永远不变:接收函数 → 返回 wrapper → wrapper 里调用原函数并处理增强逻辑。
带参数的装饰器 再加一层函数传参即可。
多装饰器 叠加时注意装饰顺序(从下往上)和调用顺序(从上往下)。
类装饰器 让装饰器能够持有状态,结构更直观。
装饰器在 Web 框架、权限校验、日志、缓存等领域无处不在,是 Pythonic 代码的重要标志。
掌握了闭包和装饰器,你就掌握了 Python 函数式编程的一大半,也为理解后面的类装饰器、functools.wraps 等高级话题铺平了道路。
如果觉得这篇文章对你有帮助,欢迎点赞、在看、转发给同样在学习 Python 的朋友~谢谢~~~