ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

python-第24天:Python中的装饰器详解

2026/9/4 17:26:21 拓冰建站 浏览量
python-第24天:Python中的装饰器详解 30天入门Python基础篇——第24天Python中的装饰器详解学习日期第24天 | ⏱️预计用时60分钟 | 难度等级⭐⭐⭐⭐ 学习目标理解装饰器的本质和作用掌握装饰器的基本语法和使用方法学会编写带参数的装饰器理解functools.wraps的作用掌握类装饰器的使用方法能够编写多个装饰器叠加的复杂场景一、什么是装饰器1.1 装饰器的概念装饰器Decorator是 Python 中一个非常强大且优雅的特性。从本质上讲装饰器就是一个函数它接受一个函数作为参数返回一个新的函数。装饰器允许我们在不修改原有函数代码的情况下为其添加额外的功能。打个比方装饰器就像给手机套上一个手机壳。手机本身的功能不变但手机壳给它增加了防摔、美观等额外特性。1.2 装饰器的设计原则装饰器遵循开放-封闭原则Open-Closed Principle对扩展开放可以给函数添加新功能对修改封闭不需要修改原函数的代码二、函数装饰器的基础2.1 最简单的装饰器defmy_decorator(func):一个简单的装饰器defwrapper():print(在函数执行之前)func()print(在函数执行之后)returnwrappermy_decoratordefsay_hello():print(Hello!)# 调用被装饰的函数say_hello()输出在函数执行之前 Hello! 在函数执行之后2.2 装饰器的执行流程my_decorator def say_hello(): ...等价于defsay_hello():...say_hellomy_decorator(say_hello)2.3 原理解析让我们逐步拆解上面的装饰器定义阶段Python 解释器读到my_decorator时会立即调用my_decorator(say_hello)并将返回值重新赋值给say_hello调用阶段当我们调用say_hello()时实际调用的是wrapper()函数执行阶段wrapper()内部调用了原始的func()即原say_hello并在其前后添加了额外逻辑三、带参数的函数装饰器3.1 装饰带参数的函数上面的装饰器只能装饰无参数的函数。实际开发中我们需要处理带参数的函数defmy_decorator(func):defwrapper(*args,**kwargs):print(f调用函数{func.__name__}参数:{args},{kwargs})resultfunc(*args,**kwargs)print(f函数{func.__name__}执行完毕返回值:{result})returnresultreturnwrappermy_decoratordefadd(a,b):returnabmy_decoratordefgreet(name,age20):print(f你好,{name}! 你今年{age}岁。)# 测试add(3,5)greet(小明,age25)输出调用函数 add参数: (3, 5), {} 函数 add 执行完毕返回值: 8 调用函数 greet参数: (小明,), {age: 25} 你好, 小明! 你今年 25 岁。 函数 greet 执行完毕返回值: None3.2 关键点*args和**kwargs*args接收所有位置参数打包成元组**kwargs接收所有关键字参数打包成字典这种写法使装饰器可以适配任意参数签名的函数四、带参数的装饰器有时候我们需要给装饰器本身传递参数比如指定重试次数、日志级别等。4.1 语法结构带参数的装饰器需要三层嵌套defrepeat(times):装饰器工厂控制函数重复执行次数defdecorator(func):defwrapper(*args,**kwargs):foriinrange(times):print(f第{i1}次执行:)resultfunc(*args,**kwargs)returnresultreturnwrapperreturndecoratorrepeat(3)defsay_hi():print(Hi!)say_hi()输出第 1 次执行: Hi! 第 2 次执行: Hi! 第 3 次执行: Hi!4.2 执行流程解析repeat(3)defsay_hi():...等价于defsay_hi():...say_hirepeat(3)(say_hi)# ↑ 先执行 ↑ 再执行repeat(3)先执行返回decorator函数decorator(say_hi)再执行返回wrapper函数最终say_hi指向wrapper4.3 实战带参数的重试装饰器importtimedefretry(max_retries3,delay1):重试装饰器函数失败时自动重试defdecorator(func):defwrapper(*args,**kwargs):forattemptinrange(max_retries):try:returnfunc(*args,**kwargs)exceptExceptionase:print(f第{attempt1}次尝试失败:{e})ifattemptmax_retries-1:print(f等待{delay}秒后重试...)time.sleep(delay)print(所有重试均失败)returnwrapperreturndecorator# 模拟一个可能失败的函数importrandomretry(max_retries3,delay0.5)deffetch_data():ifrandom.random()0.7:# 70% 概率失败raiseConnectionError(网络连接失败)return数据获取成功!resultfetch_data()五、functools.wraps的作用5.1 问题引入使用装饰器后原函数的元信息函数名、文档字符串等会丢失defmy_decorator(func):defwrapper(*args,**kwargs):wrapper 的文档字符串returnfunc(*args,**kwargs)returnwrappermy_decoratordefgreet(name):向用户打招呼print(fHello,{name}!)print(f函数名:{greet.__name__})# 输出: wrapperprint(f文档字符串:{greet.__doc__})# 输出: wrapper 的文档字符串5.2 解决方案wrapsfromfunctoolsimportwrapsdefmy_decorator(func):wraps(func)# ← 关键保留原函数的元信息defwrapper(*args,**kwargs):wrapper 的文档字符串returnfunc(*args,**kwargs)returnwrappermy_decoratordefgreet(name):向用户打招呼print(fHello,{name}!)print(f函数名:{greet.__name__})# 输出: greetprint(f文档字符串:{greet.__doc__})# 输出: 向用户打招呼5.3 最佳实践所有装饰器都应该使用wraps这是一个良好的编程习惯。它能保留原函数名__name__保留原函数文档__doc__保留原函数的其他元信息对调试、日志、自省等场景非常重要六、多个装饰器的叠加6.1 装饰器链一个函数可以同时被多个装饰器装饰defdecorator_a(func):wraps(func)defwrapper(*args,**kwargs):print([A] 执行前)resultfunc(*args,**kwargs)print([A] 执行后)returnresultreturnwrapperdefdecorator_b(func):wraps(func)defwrapper(*args,**kwargs):print([B] 执行前)resultfunc(*args,**kwargs)print([B] 执行后)returnresultreturnwrapperdecorator_adecorator_bdefhello():print(Hello World!)hello()输出[A] 执行前 [B] 执行前 Hello World! [B] 执行后 [A] 执行后6.2 执行顺序装饰器的执行顺序是从内到外从下到上decorator_a# 最外层最后执行decorator_b# 内层先执行deffunc():...等价于funcdecorator_a(decorator_b(func))七、类装饰器装饰器不仅可以是函数也可以是类。7.1 类作为装饰器classCounter:统计函数调用次数的装饰器def__init__(self,func):self.funcfunc self.count0def__call__(self,*args,**kwargs):self.count1print(f第{self.count}次调用)returnself.func(*args,**kwargs)Counterdefsay_hello():print(Hello!)say_hello()say_hello()say_hello()print(f总共调用了{say_hello.count}次)输出第 1 次调用 Hello! 第 2 次调用 Hello! 第 3 次调用 Hello! 总共调用了 3 次7.2 关键点类作为装饰器时需要实现__call__方法__init__接收被装饰的函数__call__在每次调用函数时执行八、实战案例8.1 计时装饰器importtimefromfunctoolsimportwrapsdeftimer(func):计算函数执行时间的装饰器wraps(func)defwrapper(*args,**kwargs):starttime.time()resultfunc(*args,**kwargs)endtime.time()print(f{func.__name__}执行耗时:{end-start:.6f}秒)returnresultreturnwrappertimerdefslow_function():模拟一个耗时操作time.sleep(1)return完成slow_function()8.2 日志装饰器importloggingfromfunctoolsimportwraps logging.basicConfig(levellogging.INFO,format%(asctime)s - %(message)s)deflog(func):记录函数调用的日志装饰器wraps(func)defwrapper(*args,**kwargs):logging.info(f调用{func.__name__}({, .join(map(str,args))},{kwargs}))resultfunc(*args,**kwargs)logging.info(f{func.__name__}返回{result})returnresultreturnwrapperlogdefmultiply(a,b):returna*b multiply(6,7)8.3 缓存装饰器fromfunctoolsimportwrapsdefcache(func):简单的缓存装饰器cache_dict{}wraps(func)defwrapper(*args):ifargsincache_dict:print(f从缓存中获取:{args})returncache_dict[args]resultfunc(*args)cache_dict[args]resultprint(f计算并缓存:{args})returnresultreturnwrappercachedeffibonacci(n):计算斐波那契数列ifn1:returnnreturnfibonacci(n-1)fibonacci(n-2)print(fibonacci(5))九、常见错误和注意事项9.1 忘记返回 wrapper# ❌ 错误没有返回 wrapperdefbad_decorator(func):defwrapper(*args,**kwargs):print(before)func(*args,**kwargs)print(after)# 这里缺少 return wrapper# ✅ 正确defgood_decorator(func):defwrapper(*args,**kwargs):print(before)returnfunc(*args,**kwargs)returnwrapper# ← 必须返回9.2 忘记返回函数结果# ❌ 错误没有返回 resultdefwrapper(*args,**kwargs):func(*args,**kwargs)# 返回值被丢弃# ✅ 正确defwrapper(*args,**kwargs):returnfunc(*args,**kwargs)# 保留返回值9.3 类方法装饰器中的 self装饰类方法时第一个参数是self要注意defmethod_logger(func):wraps(func)defwrapper(self,*args,**kwargs):print(f调用{self.__class__.__name__}.{func.__name__})returnfunc(self,*args,**kwargs)returnwrapper十、总结知识点关键内容装饰器本质接受函数作为参数返回新函数的高阶函数基本语法decorator放在函数定义上方参数处理使用*args, **kwargs适配任意参数装饰器参数需要三层嵌套函数元信息保留使用wraps(func)多个装饰器从下到上从内到外执行类装饰器实现__init__和__call__方法核心要点回顾装饰器 函数包装器可以在不修改原函数的情况下添加功能wraps是必备保留函数名和文档字符串*args, **kwargs是标配让装饰器可以装饰任何函数三层嵌套是带参数装饰器的标准写法多个装饰器叠加时注意执行顺序是从下到上十一、练习题练习 1编写权限检查装饰器编写一个装饰器检查用户是否有权限调用某函数。如果用户名不在允许列表中打印 “无权限”。练习 2编写性能监控装饰器编写一个装饰器统计并打印函数的执行时间如果超过指定阈值则发出警告。练习 3编写 HTML 标签装饰器编写一个装饰器将函数的字符串返回值包裹在指定的 HTML 标签中如p,div。# 期望效果html_tag(p)defget_text():returnHello Worldprint(get_text())# 输出: pHello World/p参考答案练习 1 参考答案fromfunctoolsimportwrapsdefrequire_permission(allowed_users):defdecorator(func):wraps(func)defwrapper(user,*args,**kwargs):ifusernotinallowed_users:print(f用户 {user} 无权限调用{func.__name__})returnNonereturnfunc(user,*args,**kwargs)returnwrapperreturndecoratorrequire_permission([admin,manager])defdelete_record(user,record_id):print(f用户{user}删除了记录{record_id})delete_record(admin,101)delete_record(guest,102)练习 2 参考答案importtimefromfunctoolsimportwrapsdeftime_limit(max_seconds):defdecorator(func):wraps(func)defwrapper(*args,**kwargs):starttime.time()resultfunc(*args,**kwargs)elapsedtime.time()-startifelapsedmax_seconds:print(f⚠️ 警告:{func.__name__}耗时{elapsed:.2f}s超过阈值{max_seconds}s)else:print(f✅{func.__name__}耗时{elapsed:.4f}s)returnresultreturnwrapperreturndecorator练习 3 参考答案fromfunctoolsimportwrapsdefhtml_tag(tag_name):defdecorator(func):wraps(func)defwrapper(*args,**kwargs):resultfunc(*args,**kwargs)returnf{tag_name}{result}/{tag_name}returnwrapperreturndecoratorhtml_tag(p)defget_text():returnHello Worldprint(get_text())# pHello World/p 恭喜你完成了第24天的学习装饰器是 Python 中非常重要的高级特性掌握它将让你的代码更加优雅和模块化。下一天预告第25天 —— Python中模块与包详解