Python 如何实现一个带状态的装饰器?
技术百科
舞夢輝影
发布时间:2026-01-19
浏览: 次 带状态的装饰器是能保存和访问内部变量的装饰器,常用类或闭包实现:类方式通过__call__和实例属性管理状态,支持多实例隔离与扩展;闭包方式用nonlocal修改外层变量,适合轻量单状态场景;参数化装饰器推荐类实现,如限流器;需用functools.wraps保留原函数元信息,避免全局变量共享状态。
带状态的装饰器是指在装饰过程中能保存和访问内部变量的装饰器,比如统计调用次数、缓存上次结果、限制调用频率等。核心思路是让装饰器本身是一个类,或返回一个闭包,其中包含可变的状态变量。
用类实现带状态的装饰器
类是最清晰、最易扩展的方式。通过实现 __call__ 方法,让实例可调用;状态(如计数器)作为实例属性保存。
例如,实现一个统计函数调用次数的装饰器:
class CallCounter:
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)@CallCounter
def greet(name):
return f"Hello, {name}!"
使用时:
greet("Alice") # 输出:greet 已被调用 1 次
greet("Bob") # 输出:greet 已被调用 2 次
print(greet.count) # 输出:2
用闭包实现带状态的装饰器
闭包方式利用嵌套函数+ nonlocal 声明来修改外层变量。适合轻量、单状态场景。
同样实现调用计数:
def call_counter(func):
count = 0
def wrapper(*args, **kwargs):
nonlocal count
count += 1
print(f"{func.__name__} 已被调用 {count} 次")
return func(*args, **kwargs)
wrapper.count = lambda: count # 提供访问接口(可选)
return wrapper
@call_counter
def say_hi():
return "Hi!"
注意:闭包中的 count 是不可直接赋值修改的(除非用 nonlocal),且无法像类那样自然支持多个独立实例。
支持参数的带状态装饰器(类方式更推荐)
如果需要初始化状态(比如设定最大调用次数),类方式更直观:
class RateLimiter:
def __init__(self, max_calls=3):
self.max_calls = max_calls
self.calls = 0
def __call__(self, func):
def wrapper(*args, **kwargs):
if self.calls >= self.max_calls:
raise RuntimeError(f"超出调用上限 {self.max_calls}")
self.calls += 1
print(f"第 {self.calls} 次调用")
re
turn func(*args, **kwargs)
return wrapper使用
@RateLimiter(max_calls=2)
def do_work():
return "done"
这种写法中,@RateLimiter(...) 先构造实例,再用该实例装饰函数,状态随实例隔离,不同装饰器互不影响。
注意事项与技巧
- 用 functools.wraps 包装内层函数(无论是类的 __call__ 还是闭包的 wrapper),避免丢失原函数的 __name__、__doc__ 等元信息
- 类装饰器天然支持多实例状态隔离;闭包装饰器若带参数,需再嵌套一层函数,容易混淆作用域
- 状态变量应避免全局变量——否则所有被装饰函数共享同一状态,通常不是预期行为
- 若需重置状态(如测试中),类方式可通过实例方法暴露 reset 接口,闭包方式则较难做到
# ai
# 是一个
# 可选
# 多个
# 是指
# python
# 可通过
# 已被
# app
# 再用
# 接口
# 作用域
# 闭包
# count
# 全局变量
# 最易
# 中能
相关栏目:
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
AI推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
SEO优化<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
技术百科<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
谷歌推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
百度推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
网络营销<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
案例网站<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
精选文章<?muma echo $count; ?>
】
相关推荐
- php后缀怎么变mp4能播放_让php伪装mp4正
- 如何使用正则表达式批量替换重复的星号-短横模式为固
- ACF 教程:正确更新嵌套在多层 Group 字段
- 如何在Golang中解压文件_Golang com
- 如何在Golang中捕获HTTP服务器错误_Gol
- Win11如何设置ipv6 Win11开启IPv6
- Go语言中CookieJar的持久化机制解析:内存
- 如何高效获取循环末次生成的 NumPy 数组最后一
- windows如何测试网速_windows系统网络
- PHP主流架构如何处理会话管理_Session与C
- c# Task.ConfigureAwait(tr
- Win11开机速度慢怎么优化_Win11系统启动加
- php订单日志怎么记录评价_php记录订单评价日志
- Windows10怎么查看硬件信息_Windows
- Win11怎么关闭专注助手 Win11关闭免打扰模
- 如何使用Golang搭建本地API测试环境_快速验
- 如何在JavaScript中动态拼接PHP的bas
- php485返回空数组怎么回事_php485数据接
- 如何用列表一次性对 DataFrame 的指定列应
- Win11怎么开启空间音效_Windows11耳机
- c++中如何求一个数的平方根_c++ sqrt函数
- Win10怎样安装Excel数据分析工具_Win1
- Python字符串处理进阶_切片方法解析【指导】
- Win11关机快捷键是什么_Win11快速关机方法
- Win11怎么自动隐藏任务栏_Win11全屏显示设
- Win11怎么忘记WiFi网络_Win11删除已保
- win11 OneDrive怎么彻底关闭 Win1
- Windows怎样拦截WPS弹窗广告_Window
- 如何在JavaScript中动态拼接PHP的bas
- php接口返回数据乱码怎么办_php接口调试编码问
- Win10闹钟铃声怎么自定义 Win10闹钟自定义
- Python装饰器设计思路_功能增强机制说明【指导
- Win10如何卸载WindowsDefender_
- php8.4新语法match怎么用_php8.4m
- 如何使用Golang编写单元测试_创建Test函数
- Win10怎样卸载iTunes_Win10卸载iT
- Win11怎么查看显卡温度 Win11任务管理器查
- Win11声音忽大忽小怎么办 Win11音频增强功
- windows 10专注助手怎么关闭_window
- Python面向对象实战讲解_类与设计模式深入理解
- Win11怎样彻底卸载自带应用_Win11彻底卸载
- c++的STL算法库find怎么用 在容器中查找指
- Windows蓝屏错误0x00000018怎么处理
- c++如何判断文件是否存在_c++ filesys
- Win11怎么恢复误删照片_Win11数据恢复工具
- Windows10系统服务优化指南_Win10禁用
- Drupal 中 HTML 链接被双重转义导致渲染
- Python文本编码与解码_跨平台解析说明【指导】
- 如何在Golang中实现服务熔断与限流_Golan
- LINUX如何开放防火墙端口_Linux fire


QQ客服