Python 装饰器,人剑要合一

发布时间:2026/9/21 22:14:36

Python 装饰器,人剑要合一 装饰器Decorator是 Python 中非常强大且灵活的特性它能让我们在不修改原函数代码的前提下为函数增加额外的功能。它常用于日志记录、权限校验、性能测试、缓存等场景。很多初学者觉得装饰器既神奇又难懂其实只要掌握了闭包和高阶函数的基础就能轻松理解。本文会从最简单的函数讲起逐步带你掌握装饰器的各种进阶用法并配有丰富示例和场景拓展。一、一切始于“函数是一等公民”在 Python 中函数和其他变量一样可以赋值给另一个变量、作为参数传递甚至作为返回值。这是实现装饰器的前提。defgreet(name):returnfHello,{name}!# 将函数赋值给新变量say_hellogreetprint(say_hello(World))# 输出Hello, World!# 函数作为参数defexecute(func,arg):returnfunc(arg)print(execute(greet,Python))# 输出Hello, Python!当函数能作为参数传递也就意味着我们可以编写一个“函数工厂”动态地包装其他函数的行为。二、闭包装饰器的核心闭包是指在一个内部函数中引用了外部函数的变量并且外部函数返回了这个内部函数。这种结构可以“记住”外部变量的状态。defouter(message):definner():print(f记住的信息是:{message})returninner fnouter(hello closure)fn()# 输出记住的信息是: hello closureinner函数使用了外部作用域的message即便outer已经执行完毕message依然被保留。装饰器的本质就是这种“接受一个函数返回一个闭包包装后的函数”的模式。三、最简单的装饰器现在我们来写一个“无参装饰器”它在函数执行前后打印日志。defsimple_logger(func):defwrapper():print(f调用函数{func.__name__}之前)resultfunc()print(f调用函数{func.__name__}之后)returnresultreturnwrapper# 使用装饰器语法糖 simple_loggerdefsay_hi():print(Hi!)returndoneresultsay_hi()print(返回值:,result)输出调用函数 say_hi 之前 Hi! 调用函数 say_hi 之后 返回值: done为什么加上 函数就可以作为装饰器使用 是语法糖 其本质是 函数调用。simple_loggerdefsay_hi():print(Hi!)returndoneresultsay_hi()print(返回值:,result)等价于:defsay_hi():print(Hi!)returndonesay_hisimple_logger(say_hi)# 返回值 接收变量 必须和 函数名 一致say_hiresultsay_hi()print(返回值:,result)simple_logger 放在函数定义上方等价于在函数定义完之后手动执行一行任何函数都可以加上 成为装饰器吗不是的。 能被 使用的函数必须满足装饰器的签名契约能成为 装饰器 的 函数 要求1、输入函数第一个参数必须是函数 2、输出函数四、给装饰的函数传递参数上面的装饰器只适用于无参数的函数。如果原函数有参数呢很简单让wrapper接受任意参数*args, **kwargs并转发即可。deflogger(func):defwrapper(*args,**kwargs):print(f调用{func.__name__}参数:{args},{kwargs})resultfunc(*args,**kwargs)print(f结束{func.__name__}返回值:{result})returnresultreturnwrapperloggerdefadd(a,b):returnab add(3,5)输出调用 add参数: (3, 5), {} 结束 add返回值: 8这样一来装饰器就可以作用于任意参数的函数了。*args收集所有位置参数为一个元组**kwargs收集所有关键字参数为一个字典转发时解包即可。五、保留原函数的元信息经过装饰后say_hi.__name__变成了wrapper文档字符串也会丢失。这会给调试和依赖元信息的工具带来困扰。我们可以使用标准库functools.wraps来解决。fromfunctoolsimportwrapsdeflogger(func):wraps(func)defwrapper(*args,**kwargs):print(f调用{func.__name__})returnfunc(*args,**kwargs)returnwrapperloggerdefmultiply(x,y):返回两数乘积returnx*yprint(multiply.__name__)# multiplyprint(multiply.__doc__)# 返回两数乘积wraps(func)将原函数的__name__、__doc__、__module__等属性拷贝到wrapper上保持了元信息。六、带参数的装饰器有时我们希望装饰器本身能接受一些参数比如日志级别、重试次数等。这需要在普通装饰器外再包装一层函数用来接收这些配置参数然后返回真正的装饰器。fromfunctoolsimportwrapsimporttimedefretry(max_attempts3,delay1):装饰器工厂返回一个根据参数配置的装饰器defdecorator(func):wraps(func)defwrapper(*args,**kwargs):attempts0whileattemptsmax_attempts:try:returnfunc(*args,**kwargs)exceptExceptionase:attempts1print(f第{attempts}次失败:{e})ifattemptsmax_attempts:raisetime.sleep(delay)returnwrapperreturndecoratorretry(max_attempts4,delay0.5)defunstable_network_call():# 模拟偶发异常importrandomifrandom.random()0.7:raiseConnectionError(网络不稳定)return数据获取成功print(unstable_network_call())这里retry(max_attempts, delay)返回decoratordecorator才是真正的装饰器。记住带参数的装饰器本质就是“装饰器工厂”多一层函数调用。七、多个装饰器的叠加与顺序一个函数可以应用多个装饰器它们会自下而上地执行包装。离函数最近的装饰器先包装然后依次向外。defupper_decorator(func):wraps(func)defwrapper(*args,**kwargs):resultfunc(*args,**kwargs)ifisinstance(result,str):returnresult.upper()returnresultreturnwrapperdefbold_decorator(func):wraps(func)defwrapper(*args,**kwargs):resultfunc(*args,**kwargs)ifisinstance(result,str):returnfb{result}/breturnresultreturnwrapperbold_decoratorupper_decoratordefgreeting(name):returnfhello{name}print(greeting(World))执行流程greeting先被upper_decorator包装结果变为大写然后被bold_decorator包装最终结果变成bHELLO WORLD/b。如果交换装饰器顺序结果会不同你可以自己试试。八、类装饰器装饰器不仅可以是函数也可以是类。类装饰器依靠__call__方法实现可调用对象。1. 无参类装饰器classCountCalls:def__init__(self,func):self.funcfunc self.count0def__call__(self,*args,**kwargs):self.count1print(f调用次数:{self.count})returnself.func(*args,**kwargs)CountCallsdefsay_hello():print(Hello!)say_hello()say_hello()__init__接收被装饰的函数__call__让实例本身可以像函数一样被调用。类装饰器便于保存状态比如这里的调用计数。2. 带参数的类装饰器classPrefixPrinter:def__init__(self,prefix):self.prefixprefixdef__call__(self,func):wraps(func)defwrapper(*args,**kwargs):print(f[{self.prefix}] 开始执行)returnfunc(*args,**kwargs)returnwrapperPrefixPrinter(DEBUG)defdo_something():print(do_something 执行中)do_something()__init__接收装饰器参数__call__接收函数并返回包装后的函数。理解这种模式后你可以自由选择函数或类来实现装饰器。九、实战场景拓展场景一性能计时器测量函数执行时间用于性能分析。importtimefromfunctoolsimportwrapsdeftimer(func):wraps(func)defwrapper(*args,**kwargs):starttime.perf_counter()resultfunc(*args,**kwargs)endtime.perf_counter()print(f{func.__name__}耗时{end-start:.6f}秒)returnresultreturnwrappertimerdefslow_calculation():total0foriinrange(1000000):totalireturntotal slow_calculation()场景二权限验证在 Web 开发中可用装饰器检查用户是否登录或具有权限。current_user{username:Alice,is_admin:True}defrequire_admin(func):wraps(func)defwrapper(*args,**kwargs):ifnotcurrent_user.get(is_admin):raisePermissionError(需要管理员权限)returnfunc(*args,**kwargs)returnwrapperrequire_admindefdelete_user(user_id):returnf用户{user_id}已被删除print(delete_user(42))场景三缓存函数结果备忘避免重复计算提高效率。defmemoize(func):cache{}wraps(func)defwrapper(*args):ifargsincache:print(从缓存返回)returncache[args]resultfunc(*args)cache[args]resultreturnresultreturnwrappermemoizedeffibonacci(n):ifn2:returnnreturnfibonacci(n-1)fibonacci(n-2)print(fibonacci(30))# 第二次调用时速度极大提升场景四单例模式利用类装饰器实现单例也可用装饰器修饰类。defsingleton(cls):instances{}wraps(cls)defget_instance(*args,**kwargs):ifclsnotininstances:instances[cls]cls(*args,**kwargs)returninstances[cls]returnget_instancesingletonclassDatabaseConnection:def__init__(self,db_name):self.db_namedb_nameprint(f连接数据库{db_name})conn1DatabaseConnection(mydb)conn2DatabaseConnection(mydb)print(conn1isconn2)# True场景五参数类型校验在函数调用时自动校验参数类型。deftype_check(*param_types):defdecorator(func):wraps(func)defwrapper(*args,**kwargs):forarg,expected_typeinzip(args,param_types):ifnotisinstance(arg,expected_type):raiseTypeError(f参数{arg}不是{expected_type})returnfunc(*args,**kwargs)returnwrapperreturndecoratortype_check(int,int)defadd_numbers(a,b):returnabprint(add_numbers(1,2))# 正常# add_numbers(1, 2) # 抛出 TypeError十、装饰器与上下文管理、描述符的联动装饰器并不是孤立的它经常和上下文管理器with、描述符等特性一起使用构建更优雅的代码。例如用装饰器实现一个临时修改环境变量执行后自动恢复的功能importosfromcontextlibimportcontextmanagercontextmanagerdefset_env_var(key,value):oldos.environ.get(key)os.environ[key]valuetry:yieldfinally:ifoldisNone:os.environ.pop(key,None)else:os.environ[key]olddefwith_env(key,value):装饰器在函数执行期间设置环境变量defdecorator(func):wraps(func)defwrapper(*args,**kwargs):withset_env_var(key,value):returnfunc(*args,**kwargs)returnwrapperreturndecoratorwith_env(MY_VAR,hello)defread_env():print(os.environ.get(MY_VAR))read_env()# 输出helloprint(os.environ.get(MY_VAR))# None已恢复这种组合让资源的准备和清理变得极其安全。十一、注意事项与最佳实践保持元信息务必使用wraps(func)便于调试和文档生成。不要滥用装饰器会引入额外抽象如果不是必要的横切关注点直接用普通函数调用更清晰。注意参数传递设计通用装饰器时永远使用*args, **kwargs避免限制被装饰函数的签名。装饰器顺序多个装饰器叠加时注意执行顺序外层的先执行内层的先包装。调试支持Python 3.10 可以使用functools.update_wrapper或wraps保留更好的内省信息functools.cache等内置装饰器也可以直接使用无需自己实现缓存。类型提示如果你使用类型检查可以用Callable、泛型等为装饰器标注类型提高代码可读性。十二、总结装饰器本质上就是“接受一个函数返回一个新函数”的高阶函数或可调用对象。从无参装饰器到带参数的装饰器工厂再到类装饰器它们都遵循这个核心思想。掌握了闭包和*args, **kwargs的用法后你可以用装饰器优雅地实现日志、缓存、权限、事务、性能分析等横切关注点。Python 标准库中就有许多装饰器比如staticmethod、classmethod、property、functools.lru_cache等它们都是装饰器应用的典范。建议多动手实践把装饰器融入到日常开发中你会发现代码的复用性和可读性都会有质的飞跃。
延伸阅读

更多相关文章

2026/9/21 22:14:36

历史周报语义搜索与知识问答:基于向量相似度的智能回忆录

历史周报语义搜索与知识问答:基于向量相似度的智能回忆录在很多企业或个人开发者的知识资产积累中,工作周报沉淀了最宝贵的“技术排障实录、架构演进决策与踩坑解决方案”。 然而,当用户在半年后遇到一个相似的线上事故时: “我记…

2026/9/21 22:14:36

独立开发者代码重构AI工具链:Cursor与Claude3.5实测

独立开发者代码重构AI工具链:Cursor与Claude3.5实测在 2026 年的 AI 辅助编程领域,软件开发范式已经从“在网页对话框里让 ChatGPT 生成单段代码并人肉复制”,全面演进为了“原生嵌入 IDE、理解全局代码库(Full-Codebase Awarenes…

2026/9/21 22:14:36

脸上痘印怎么去除实战指南:从入门到精通的底层逻辑

脸上痘印怎么去除实战指南:从入门到精通的底层逻辑 复制来的代码跑不通,报错信息一片红,到底卡在哪儿?这种“看着别人能跑,自己就是不行”的挫败感,是每个刚入行的应届生都经历过的至暗时刻。你以为是环境配置问题,重装了三遍依赖;你以为是版本冲突,…

2026/9/21 23:29:42

3个坑解决word如何添加页码源码解析避坑

3个坑解决word如何添加页码源码解析避坑 版本升级后 API 全变了?别慌,这次咱们不背黑锅。很多老手发现,以前那一套 VBA 代码或者宏指令,到了新版 Office 或者 WPS…

2026/9/21 23:29:42

营业执照模板解析:3种主流方案保姆级教程,告别配置卡壳

营业执照模板解析:3种主流方案保姆级教程,告别配置卡壳 配置环境就卡半天?别急,这篇【保姆级教程】帮你理清【营业执照模板】的技术本质。很多开发者一看到“模板”俩字就头大,觉得是设计问题,其实核心是数据结构与渲染引擎的博弈。…

2026/9/21 23:29:42

松下变频器说明书源码解析3个坑帮你搞定

松下变频器说明书源码解析3个坑帮你搞定 翻过几百页官方手册的人都知道,那密密麻麻的参数表看得人眼晕。官方文档太长抓不住重点,是大多数工程师的噩梦。今天咱们不背参数,直接上源码解析,看松下变频器底层逻辑怎么跑。…

2026/9/21 23:29:42

唯品会客服电话人工背后:3个性能优化坑,让你的系统快5倍

唯品会客服电话人工背后:3个性能优化坑,让你的系统快5倍 看了一堆教程还是不会写项目?别急着骂人,问题往往不在你智商,而在你没看懂高并发下的“性能优化”本质。 很多后端开发同学,代码写得飞起,单元测试全绿,一上生产环境,CPU…

2026/9/21 23:24:41

LangChain4j构建Java智能监督者Agent实战

1. 项目概述:LangChain4j构建监督者Agent的核心理念在当今企业级Java应用中,智能代理(Agent)系统正逐渐成为处理复杂工作流的关键组件。LangChain4j作为Java生态中的新兴框架,为开发者提供了构建这类系统的标准化工具集…

2026/9/21 3:28:31

GAMP 5 基于风险的计算机化系统验证:软件分类与审计追踪实践

简介:《A Risk-Based Approach to Compliant GxP Computerized Systems》即业内熟知的GAMP 5指南,面向制药企业质量与IT合规人员、验证工程师及计算机化系统管理者,用于解决GxP法规环境下系统合规性难以科学落地的问题。文档以风险管理为主线…

2026/9/21 3:33:19

安全托管MSSP实战:从静态防御到人机协同的攻防运营与应急响应

简介:这份PPT围绕互联网业务安全托管服务展开,面向企业安全负责人、IT运维人员及关注MSSP/MSS选型的读者,重点回应传统安全过度依赖人工、碎片化静态防御难以对抗产业化攻击等痛点。资源共1个pptx文件,包体约30.63MB,以…

2026/9/21 0:02:23

OpenResearch:构建可复现的开放式研究工作流

第一次看到“OpenResearch”这个名字,我脑子里冒出的不是某个具体软件,而更像一种研究方式的宣言:开放、可复现、可验证。这三件事放在一起,其实比大多数人想象中难得多。过去几年我一直在折腾自己的研究工作流,从纯纸…

2026/9/20 4:54:47

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/21 18:32:12

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/21 10:29:02

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码