Python装饰器是接收函数并返回新函数的高阶函数,核心是不修改原函数代码而增强功能;@语法糖等价于func = decorator(func);基本写法含嵌套函数,需用functools.wraps保留元信息;类装饰器适合维护状态;常见应用包括权限校验、缓存和重试机制。

Python装饰器本质是接收函数作为参数、返回新函数的高阶函数,核心在于“不修改原函数代码的前提下增强其功能”。关键点是理解 @ 语法糖只是语法简写,等价于 func = decorator(func)。
装饰器的基本写法(带参/不带参)
最简装饰器是一个嵌套函数:外层接收被装饰函数,内层定义执行逻辑(常含 *args, **kwargs 以兼容任意参数),最后返回内层函数。
示例(无参装饰器):
def log_call(func):
def wrapper(*args, **kwargs):
print(f"调用函数: {func.__name__}")
result = func(*args, **kwargs)
print(f"{func.__name__} 执行完毕")
return result
return wrapper
<p>@log_call
def greet(name):
return f"Hello, {name}!"</p><p>print(greet("Alice"))</p><p><span>立即学习</span>“<a href="https://pan.quark.cn/s/00968c3c2c15" style="text-decoration: underline !important; color: blue; font-weight: bolder;" rel="nofollow" target="_blank">Python免费学习笔记(深入)</a>”;</p>输出会先打印调用信息,再执行原函数,最后打印结束信息。
若需给装饰器传参(如指定日志级别),需再加一层封装:
- 最外层接收装饰器参数(如
level="INFO") - 中间层接收被装饰函数
- 最内层是实际执行逻辑
保留原函数元信息(__name__、__doc__等)
直接写装饰器会导致 greet.__name__ 变成 "wrapper",影响调试和文档生成。解决方法是使用 functools.wraps:
from functools import wraps
<p>def log_call(func):
@wraps(func) # 关键!复制原函数的元信息
def wrapper(*args, *<em>kwargs):
print(f"调用函数: {func.<strong>name</strong>}")
return func(</em>args, **kwargs)
return wrapper</p>加了 @wraps(func) 后,greet.__name__ 仍为 "greet",greet.__doc__ 也能正常显示。
类实现装饰器(支持状态与复用)
当需要在装饰过程中维护状态(如统计调用次数),用类更清晰。类需实现 __call__ 方法:
class CountCalls:
def __init__(self):
self.count = 0
<pre class="brush:php;toolbar:false;">def __call__(self, func):
@wraps(func)
def wrapper(*args, **kwargs):
self.count += 1
print(f"{func.__name__} 已被调用 {self.count} 次")
return func(*args, **kwargs)
return wrappercounter = CountCalls()
@counter def say_hi(): print("Hi!")
say_hi() # 输出:say_hi 已被调用 1 次 say_hi() # 输出:say_hi 已被调用 2 次
实战场景:权限校验与缓存装饰器
真实项目中常见需求可快速封装为装饰器:
- 权限检查:在函数执行前验证用户角色,不满足则抛出异常或返回错误
- 结果缓存:对纯计算函数(如斐波那契),用字典缓存已算结果,避免重复耗时运算
- 重试机制:对可能失败的网络请求,自动重试指定次数并捕获异常
例如缓存装饰器简化版:
def cache(func):
cached = {}
@wraps(func)
def wrapper(n):
if n not in cached:
cached[n] = func(n)
return cached[n]
return wrapper
<p>@cache
def fib(n):
if n < 2:
return n
return fib(n-1) + fib(n-2)</p>这样调用 fib(35) 不会卡住,因为子问题被缓存复用。










