尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
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等它们都是装饰器应用的典范。建议多动手实践把装饰器融入到日常开发中你会发现代码的复用性和可读性都会有质的飞跃。
RELATED

相关推荐

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

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

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

📅 2026/9/21 22:14:11
独立开发者代码重构AI工具链:Cursor与Claude3.5实测

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

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

📅 2026/9/21 22:14:10
堆栈式优化实战:3个坑让性能翻倍,面试必问

堆栈式优化实战:3个坑让性能翻倍,面试必问

堆栈式优化实战:3个坑让性能翻倍,面试必问 刚入职那会儿,我盯着屏幕上的 java.lang.StackOverflowError…

📅 2026/9/21 22:09:10
MORE NEWS

更多资讯

📰

aiohttp 修复 `CookieJar.update_cookies()` 未复制用户传入的可变 `Morsel` 对象的缺陷解析

后端Web框架WebSocket 【免费下载链接】aiohttp Asynchronous HTTP client/server framework for asyncio and Python 项目地址: https://gitcode.com/gh_mirrors/ai/aiohttp 点击查看 免费下载 本篇文章围绕 aiohttp 变更日志条目 CHANGES/13637.bugfix.rst&#…

📰

OpenSearch查询DSL完全指南:Bool、Term、Range、Wildcard等10大查询类型一篇讲透

OpenSearch查询DSL完全指南:Bool、Term、Range、Wildcard等10大查询类型一篇讲透 【免费下载链接】OpenSearch 🔎 Open source distributed and RESTful search engine. 项目地址: https://gitcode.com/gh_mirrors/op/OpenSearch OpenSearch 是一…

📰

Gyroflow 开源视频稳定工具使用指南:用陀螺仪数据消除画面抖动

Gyroflow 开源视频稳定工具使用指南:用陀螺仪数据消除画面抖动 【免费下载链接】gyroflow Video stabilization using gyroscope data 项目地址: https://gitcode.com/GitHub_Trending/gy/gyroflow Gyroflow 是一款开源的视频稳定工具,它直接读取…

📰

免费 3 步下载流媒体:DASH/HLS 课程与直播的本地保存方法

免费 3 步下载流媒体:DASH/HLS 课程与直播的本地保存方法 【免费下载链接】N_m3u8DL-RE Cross-Platform, modern and powerful stream downloader for MPD/M3U8/ISM. English/简体中文/繁體中文. 项目地址: https://gitcode.com/GitHub_Trending/nm3/N_m3u8DL-RE…

📰

5个sina邮箱开发避坑点:新手速查手册

5个sina邮箱开发避坑点:新手速查手册 sina邮箱的开发文档太厚,新人根本抓不住重点。别翻那几百页的PDF了,直接看这份速查手册。…

📰

公租房摇号时间源码深度剖析:3个技巧搞定性能优化

公租房摇号时间源码深度剖析:3个技巧搞定性能优化 官方文档几百页,翻到头晕还是找不到核心逻辑?别急,公租房摇号时间的计算看似简单,实则是高并发场景下的性能优化典型。今天拆解开源实现,直接看代码。 入口定位:从请求到计算的全链路…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬