当前位置: 首页 > news >正文

装饰器 (中): 进阶篇,解锁框架级玩法

基础看完了?那我们来玩点进阶的。

你有没有好奇过,FastAPI 的@depends()、Flask 的@app.route()都是怎么写的?为啥人家的装饰器能传参数,还能这么灵活?

今天我就把这些进阶玩法教给你,学会了,你也能写出灵活的装饰器,看懂各种框架的源码。

1. 带参数的装饰器,了解一下?

有时候你可能会想,能不能给装饰器自己也传点参数?比如我想要一个重复执行 N 次的装饰器,N 我自己说了算。

这也简单,就是再包一层嘛!

三层函数的秘密

普通的装饰器是两层:外部函数接收原函数,内部函数是包装后的函数。

那带参数的装饰器,就是再加一层:最外层接收装饰器的参数,然后返回真正的装饰器。

我们来看个最经典的例子:

defrepeat(times):# 这一层:接收装饰器的参数,比如times=3defdecorator(func):# 这一层:接收原函数,就是普通的装饰器了@functools.wraps(func)defwrapper(*args,**kwargs):# 这一层:真正的包装函数,执行原函数result=Nonefor_inrange(times):result=func(*args,**kwargs)returnresultreturnwrapperreturndecorator# 用一下!@repeat(times=3)defsay_hello(name):print(f"Hello,{name}!")say_hello("Alice")# 输出:# Hello, Alice!# Hello, Alice!# Hello, Alice!

看到了吗?@repeat(times=3)其实就是先调用repeat(3),它返回了一个装饰器,然后用这个装饰器去装饰say_hello函数。

等价于:say_hello = repeat(times=3)(say_hello),就这么简单!

更实用的例子:带级别的日志装饰器

我们来写一个更实用的,你可以指定日志的级别,是 info 还是 debug:

importloggingdeflog(level="info"):defdecorator(func):@functools.wraps(func)defwrapper(*args,**kwargs):# 根据你指定的级别来打日志msg=f"调用函数:{func.__name__}"iflevel=="debug":logging.debug(msg)else:logging.info(msg)returnfunc(*args,**kwargs)returnwrapperreturndecorator# 用一下!@log(level="debug")defdebug_function():# 这个函数的日志会打debug级别pass@log()defnormal_function():# 这个函数的日志会打默认的info级别pass

🌟 这就是框架的秘密:FastAPI 的@depends()、Flask 的@app.route(),本质上都是这个模式!学会了这个,你看很多框架的源码就都能看懂了。

2. 多个装饰器一起用,注意顺序!

你可以给一个函数加多个装饰器,但是这里有个超级容易踩的坑:装饰器的执行顺序,很多人搞反了

我们来看个实际的例子,你就懂了:

defdecorator1(func):@functools.wraps(func)defwrapper():print("进入 decorator1")func()print("退出 decorator1")returnwrapperdefdecorator2(func):@functools.wraps(func)defwrapper():print("进入 decorator2")func()print("退出 decorator2")returnwrapper@decorator1@decorator2defhello():print("Hello!")hello()

你猜输出是什么?

进入 decorator1 进入 decorator2 Hello! 退出 decorator2 退出 decorator1

哦!原来装饰器是从下到上装饰,从上到下执行

也就是说,@decorator1在最上面,它先执行,然后才是@decorator2,最后才是原函数。

等价于:hello = decorator1(decorator2(hello)),所以调用的时候,先调用 decorator1 的 wrapper,然后它调用 decorator2 的 wrapper,然后它调用原函数。

⚠️踩坑提醒:顺序很重要!比如你写 Web 接口,@login_required@permission_required,一定要把@login_required放在上面,不然未登录的用户会先去检查权限,直接报错!

比如:

# 正确的顺序@login_required@permission_required("delete_user")defdelete_user(user_id):# ...

这样,用户先检查登录,没登录直接跳登录页,不会去检查权限了。

3. 类装饰器,给类也来个包装

除了函数,装饰器还能用来装饰类!这在写 ORM、写框架的时候特别有用。

类装饰器的原理很简单:它接收一个类,然后返回一个新的类(或者修改后的类)。

我们来写一个最实用的:自动给类加一个好看的__repr__方法,这样你打印对象的时候,就能自动看到所有的属性了:

defadd_repr(cls):# 这就是类装饰器,接收类cls@functools.wraps(cls)def__repr__(self):# 自动生成__repr__,把所有的属性都列出来attrs=", ".join(f"{k}={v!r}"fork,vinself.__dict__.items())returnf"{cls.__name__}({attrs})"# 给类加上这个方法cls.__repr__=__repr__returncls# 用一下!@add_reprclassPerson:def__init__(self,name,age):self.name=name self.age=age p=Person("Alice",25)print(p)# 输出: Person(name='Alice', age=25)

是不是超方便?不用你自己写__repr__了,加个装饰器就搞定了!

这个在写 ORM 框架的时候特别有用,比如 SQLAlchemy 的模型类,很多都是用类装饰器来自动注册的。

4. 用类来实现装饰器,保存状态更方便

如果你的装饰器需要维护复杂的状态,用类比用闭包更好,代码更清晰,也更容易测试。

因为类可以很自然的保存状态,用self.xxx就可以了,不用像闭包那样搞 nonlocal。

原理也很简单:装饰器接收原函数,存到self.func里,然后实现__call__方法,这样实例就能当函数用了。

我们来写一个计数器装饰器,统计函数被调用了多少次:

classCounter:def__init__(self,func):# 初始化的时候,接收原函数self.func=func self.count=0# 状态存在这里# 别忘了把原函数的信息复制过来functools.update_wrapper(self,func)def__call__(self,*args,**kwargs):# 当你调用装饰后的函数的时候,就会调用这个方法self.count+=1returnself.func(*args,**kwargs)# 用一下!@Counterdefadd(a,b):returna+bprint(add(1,2))# 输出: 3print(add(3,4))# 输出: 7print(add.count)# 输出: 2,调用了2次!

看到了吗?用类实现的话,状态的维护就非常直观,self.count一眼就能看到,比闭包的 nonlocal 好理解多了。

而且如果你的装饰器还需要带参数,也很简单,把参数放到__init__里就行:

classRepeat:def__init__(self,times):self.times=timesdef__call__(self,func):@functools.wraps(func)defwrapper(*args,**kwargs):result=Nonefor_inrange(self.times):result=func(*args,**kwargs)returnresultreturnwrapper@Repeat(times=3)defsay_hello(name):print(f"Hello,{name}")

是不是很灵活?


好了,进阶的内容差不多就这些了。现在你已经能写各种灵活的装饰器了,不管是带参数的,还是装饰类的,还是用类实现的,都没问题。

不过这些还不够,下一篇我们来点实战的,看看在实际工作中,装饰器都能用来干啥,有哪些你马上就能用到的工具。

http://www.cnnetsun.cn/news/2788076.html

相关文章:

  • 用龙邱BCMV3扩展板DIY智能小车:从电机控制到循迹避障的Python实战代码
  • 跨文化硬件项目交接:从技术冲突到协作融合的实战经验
  • 深圳电子产业工程师实战:从MCU选型到量产避坑全解析
  • 别再手动复制了!用这个工具一键生成Markdown Emoji代码,效率翻倍
  • Sunshine游戏串流性能深度调优:从零到专业的完整配置指南
  • MuleSoft企业级AI编排:构建安全可控的LLM集成中枢
  • 告别龟速下载:8大网盘直链下载助手终极指南
  • 金仓KingbaseES V8在Windows10安装后服务丢失?用sys_ctl一招搞定自启动
  • 高速公路抛洒物AI检测工具包:YOLOv8轻量模型+可视化操作界面+实测训练数据+跨平台一键部署
  • 新手友好:跟着茅佳源的教程,用快马AI生成你的第一个交互网页
  • 绿化草帘哪家靠谱
  • 避坑指南:STM32CubeMX配置PWR低功耗模式,这3个细节没做好代码白写
  • 从晶圆厂交易看半导体产业的技术传承与供应链演变
  • 从学生到工程师:掌握精确沟通与闭环思维,提升职场硬实力
  • 3分钟搞定屏幕实时翻译:Translumo终极完整指南
  • 发电机组停运容量概率建模与LOLP指标快速计算MATLAB工具集
  • 自动化库存管理系统:全链路状态建模与物理世界映射
  • MQ-2传感器数字量和模拟量输出怎么选?基于STM32的两种接入方案与避坑指南
  • 借助快马AI生成插件样板代码,自动化繁琐配置,显著提升开发效率
  • 实战指南:基于快马平台与yolov5,快速开发安全帽检测系统
  • Mythos解析:可控推理增强与可信度分级输出技术
  • 智能网盘下载革新:突破限速瓶颈的高效解决方案
  • 提示工程本质是任务翻译:从模糊需求到AI可执行指令
  • 034、SE 注意力模块:Squeeze-Excitation 的全局平均池化到 FC 到 Sigmoid 数学推导
  • RT-Thread嵌入式开发实战:从内核原理到组件应用与物联网开发
  • 如何用3步解决机械键盘连击问题?免费开源工具KeyboardChatterBlocker使用指南
  • Qt+C++编写的可运行智能门禁系统毕业设计源码(含AES加密与图形界面)
  • OpenMV 4 Plus跑TensorFlow Lite内存总报错?手把手教你优化模型和代码,告别MemoryError
  • 模板驱动型文档自动化:结构化内容与动态填充实战指南
  • 【最新版本】v2.7.9 版 OpenClaw 实操指南,零基础搭建本地桌面 AI 助手(含安装包)