
Python 装饰器新手入门与实战指南本文面向零基础读者从最基础的概念讲起手把手带你掌握 Python 装饰器的核心用法与实战技巧。WEB项目地址演示地址① 装饰器核心概念与生活化类比解析装饰器是什么装饰器Decorator是 Python 中一种强大的特性它允许我们在不修改原函数代码的情况下给函数动态地添加新功能。装饰器本质上是一个函数它接受一个函数作为参数并返回一个新的函数。生活化类比手机壳想象一下你买了一部手机原函数手机本身的功能就是打电话、发微信。后来你给手机套了一个手机壳装饰器手机还是那个手机功能一点没变但多了防摔、防滑的新功能。装饰器就是这个“手机壳”——在不改变手机本身的前提下给它增加了额外功能。装饰器的两个核心基础在学习装饰器之前必须先理解两个概念① 函数是“一等公民”在 Python 中函数和整数、字符串一样可以赋值给变量作为参数传递给其他函数作为返回值从其他函数中返回在函数内部嵌套定义② 闭包Closure闭包简单说就是在一个外层函数中定义内层函数内层函数引用外层函数的变量并且外层函数返回内层函数。即使外层函数执行完了内层函数依然能“记住”外层函数的变量。装饰器的核心结构就是基于闭包实现的。装饰器的核心价值装饰器体现了“对扩展开放对修改关闭”的开闭原则。它让你能把日志记录、性能计时、权限校验这些“通用逻辑”抽出来一次编写、到处复用让业务代码保持干净整洁。② 本地开发环境准备与版本检查装饰器是 Python 的内置特性不需要安装任何额外库只要有 Python 环境即可。检查 Python 版本打开终端Windows 用 CMD 或 PowerShellMac/Linux 用 Terminal输入python--version# 或python3--version如果显示类似Python 3.8.0或更高版本说明环境已就绪。建议使用Python 3.6 及以上版本装饰器语法在这些版本中完全支持。如果没有 PythonWindows访问 python.org 下载安装包运行安装时务必勾选“Add Python to PATH”Mac/Linux使用系统包管理器安装如sudo apt install python3 python3-pip推荐编辑器对于新手推荐使用VS Code或PyCharm社区版它们对 Python 有良好的语法高亮和调试支持。③ 函数作为对象的基础操作演示在写装饰器之前先用几个小例子感受一下“函数也是对象”示例1函数赋值给变量defadd(a,b):returnab# 把函数赋值给变量注意不加括号不是调用funcadd# 通过变量调用函数print(func(1,2))# 输出3函数add被赋值给变量func后func和add指向同一个函数对象。示例2函数作为参数defgreet(name):returnfHello,{name}!defrun_with_log(func,arg):print(准备执行函数...)resultfunc(arg)print(函数执行完毕)returnresultprint(run_with_log(greet,Alice))# 输出# 准备执行函数...# Hello, Alice!# 函数执行完毕示例3函数作为返回值闭包雏形defouter(fixed_num):definner(dynamic_num):returnfixed_numdynamic_numreturninner# 返回内层函数add5outer(5)# 生成“固定加5”的函数add10outer(10)# 生成“固定加10”的函数print(add5(3))# 输出8print(add10(4))# 输出14这里的inner就是一个闭包——它“记住”了外层函数传入的fixed_num。④ 编写第一个无参数装饰器代码有了前面的基础现在正式写一个装饰器。最简装饰器模板defmy_decorator(func):defwrapper():print(函数执行前)func()print(函数执行后)returnwrappermy_decoratordefsay_hello():print(Hello!)say_hello()# 输出# 函数执行前# Hello!# 函数执行后拆解理解my_decorator这行代码实际上就是say_hello my_decorator(say_hello)的简写。整个过程是my_decorator接收say_hello函数作为参数在my_decorator内部定义了wrapper函数在调用原函数前后添加了额外逻辑my_decorator返回wrapper函数say_hello被替换成了wrapper用 语法糖 vs 手动调用这两种写法完全等价# 方式一使用 语法糖推荐my_decoratordefsay_hello():print(Hello!)# 方式二手动调用不推荐仅用于理解原理defsay_hello():print(Hello!)say_hellomy_decorator(say_hello)⑤ 处理带参数函数的装饰器写法上面的装饰器只能装饰无参数的函数。如果被装饰的函数有参数wrapper必须能接收并传递这些参数。使用 *args 和 **kwargsdefmy_decorator(func):defwrapper(*args,**kwargs):# 接收任意参数print(函数执行前)resultfunc(*args,**kwargs)# 原封不动传过去print(函数执行后)returnresultreturnwrappermy_decoratordefadd(a,b):returnabmy_decoratordefgreet(name,greetingHello):returnf{greeting},{name}!print(add(3,5))# 输出8带提示信息print(greet(Bob))# 输出Hello, Bob!带提示信息*args接收所有位置参数**kwargs接收所有关键字参数。这样装饰器就能通用于任何函数了。⑥ 保留原函数元信息的标准实践问题装饰器会“偷走”函数身份如果不做特殊处理被装饰后的函数会丢失原有的名称和文档字符串defbad_decorator(func):defwrapper():func()returnwrapperbad_decoratordefhello():我是文档passprint(hello.__name__)# 输出wrapper不是 helloprint(hello.__doc__)# 输出None文档丢了调试的时候看到wrapper这个名字根本不知道它原来是哪个函数。解决方案functools.wraps使用functools.wraps可以将原始函数的元信息名称、文档字符串、参数列表等复制到包装函数上。fromfunctoolsimportwrapsdefgood_decorator(func):wraps(func)# 关键保留原函数元信息defwrapper(*args,**kwargs):print(执行前)resultfunc(*args,**kwargs)print(执行后)returnresultreturnwrappergood_decoratordefhello():我是文档passprint(hello.__name__)# 输出helloprint(hello.__doc__)# 输出我是文档这是一个必须养成的好习惯——所有自己写的装饰器都应该加上wraps(func)。⑦ 多个装饰器叠加使用的执行顺序Python 允许对同一个函数应用多个装饰器。执行顺序规则装饰器的应用顺序是从下往上从内到外但执行时外层先执行。看代码理解defdecorator1(func):defwrapper():print(装饰器1 前置)func()print(装饰器1 后置)returnwrapperdefdecorator2(func):defwrapper():print(装饰器2 前置)func()print(装饰器2 后置)returnwrapperdecorator1decorator2deftarget():print(目标函数)target()# 输出# 装饰器1 前置# 装饰器2 前置# 目标函数# 装饰器2 后置# 装饰器1 后置理解顺序decorator1在最外层离函数最远decorator2在里层离函数最近。等价于targetdecorator1(decorator2(target))所以执行时decorator1 的前置 → decorator2 的前置 → 原函数 → decorator2 的后置 → decorator1 的后置。⑧ 类方法装饰器与静态方法应用内置的类相关装饰器Python 提供了几个内置装饰器用于类方法装饰器作用staticmethod定义静态方法不需要实例即可调用classmethod定义类方法第一个参数是类本身property将方法变为属性访问方式示例classMyClass:staticmethoddefstatic_method():不需要实例也不需要类就像普通函数print(这是静态方法)classmethoddefclass_method(cls):第一个参数是类本身print(f这是类方法类名{cls.__name__})propertydeffull_name(self):return张三# 调用MyClass.static_method()# 不需要实例MyClass.class_method()# 不需要实例objMyClass()print(obj.full_name)# 像属性一样访问不加括号自定义装饰器应用于类方法如果想给类方法写自定义装饰器要确保自定义装饰器在classmethod或staticmethod之前fromfunctoolsimportwrapsimporttimedeftimer(func):wraps(func)defwrapper(*args,**kwargs):starttime.time()resultfunc(*args,**kwargs)print(f{func.__name__}耗时{time.time()-start:.2f}秒)returnresultreturnwrapperclassCalculator:timer# 自定义装饰器在前staticmethoddefslow_calc():time.sleep(0.5)return42print(Calculator.slow_calc())# 输出slow_calc 耗时0.50秒# 42⑨ 日志记录与性能计时实战案例案例1计时装饰器测量函数执行时间是最常用的装饰器场景之一importtimefromfunctoolsimportwrapsdeftimer(func):wraps(func)defwrapper(*args,**kwargs):starttime.time()resultfunc(*args,**kwargs)endtime.time()print(f{func.__name__}耗时{end-start:.4f}秒)returnresultreturnwrappertimerdefslow_function():time.sleep(1)return完成print(slow_function())# 输出slow_function 耗时1.0012秒# 完成案例2日志记录装饰器记录函数的调用信息和返回值便于调试fromfunctoolsimportwrapsdefdebug(func):wraps(func)defwrapper(*args,**kwargs):# 格式化参数args_str, .join(repr(arg)forarginargs)kwargs_str, .join(f{k}{v!r}fork,vinkwargs.items())all_args, .join(filter(None,[args_str,kwargs_str]))print(f调用{func.__name__}({all_args}))resultfunc(*args,**kwargs)print(f{func.__name__}返回{result!r})returnresultreturnwrapperdebugdefadd_numbers(a,b,multiply_by1):return(ab)*multiply_by add_numbers(5,3,multiply_by2)# 输出# 调用 add_numbers(5, 3, multiply_by2)# add_numbers 返回 16案例3重试装饰器带参数带参数的装饰器通过三层嵌套实现fromfunctoolsimportwrapsimporttimedefretry(max_attempts3,delay1):defdecorator(func):wraps(func)defwrapper(*args,**kwargs):forattemptinrange(1,max_attempts1):try:returnfunc(*args,**kwargs)exceptExceptionase:ifattemptmax_attempts:raiseprint(f第{attempt}次失败{delay}秒后重试...)time.sleep(delay)returnwrapperreturndecoratorretry(max_attempts3,delay0.5)defunstable_request():importrandomifrandom.random()0.7:raiseValueError(请求失败)return成功⑩ 常见缩进错误与调用异常排查错误1忘记返回 wrapperdefbad_decorator(func):defwrapper():func()# 忘记 return wrapper——返回 Nonebad_decoratordefhello():print(Hello)hello()# TypeError: NoneType object is not callable解决方法始终确保装饰器返回 wrapper 函数。错误2忘记传递 *args 和 **kwargsdefbad_decorator(func):defwrapper():# 没有参数func()# 原函数有参数时会报错returnwrapperbad_decoratordefadd(a,b):returnab add(1,2)# TypeError: wrapper() takes 0 positional arguments but 2 were given解决方法wrapper 必须使用*args, **kwargs接收并传递所有参数。错误3缩进问题Python 对缩进非常敏感。装饰器的符号和函数定义def必须在同一缩进级别# ❌ 错误 缩进和 def 不一致my_decoratordefhello():pass# ✅ 正确my_decoratordefhello():pass错误4忘记返回原函数的结果defbad_decorator(func):defwrapper(*args,**kwargs):func(*args,**kwargs)# 没有 return返回值丢失returnwrapperbad_decoratordefadd(a,b):returnabprint(add(1,2))# 输出None解决方法调用原函数后一定要return它的结果。错误5装饰器缺少 wraps不使用wraps会导致函数元信息丢失调试困难。养成好习惯所有装饰器都加上wraps(func)。总结知识点要点装饰器本质接收函数、返回函数的函数闭包无参数装饰器两层嵌套外层接收 func内层 wrapper带参数装饰器三层嵌套最外层接收装饰器参数保留元信息始终使用wraps(func)多装饰器顺序从下往上应用外层先执行通用性wrapper 使用*args, **kwargs返回值记得return原函数的执行结果装饰器是 Python 中最优雅的特性之一。从最简单的计时器开始逐步尝试写日志、重试、缓存等装饰器你会发现自己代码的复用性和整洁度都会有质的提升。