配套专栏:Python 全栈修炼之路 第 14 篇《装饰器——Python 最优雅的语法糖》难度分布:⭐ → ⭐⭐ → ⭐⭐ → ⭐⭐⭐ → ⭐⭐⭐ → ⭐⭐⭐⭐核心覆盖:闭包、@wraps、带参数装饰器、类装饰器、多层装饰器叠加、缓存/重试/权限实战前言装饰器是 Python 中最强大的语法特性之一,它让我们在不修改原函数代码的前提下,优雅地为函数添加额外功能。本练习精选 6 道编程题,从闭包基础到综合实战,帮你彻底掌握装饰器的精髓。题目一:闭包计数器与计时器 ⭐📌 题目描述利用闭包实现一个函数调用计数器和一个简易计时器:# 闭包计数器counter=make_counter()print(counter())# 1print(counter())# 2print(counter())# 3print(counter.count)# 3(查看当前计数)# 每个计数器独立counter_b=make_counter()print(counter_b())# 1print(counter())# 4(互不影响)# 闭包计时器timer=make_timer()timer.start()time.sleep(0.1)print(timer.elapsed())# ≈ 0.1 秒💡 编程思路这道题考察闭包三大要素:嵌套函数、引用外部变量、返回内部函数。make_counter:外层函数创建count变量,内层函数通过nonlocal修改它。每次调用make_counter()创建独立的闭包实例。make_timer:外层函数创建start_time变量,内层函数读取它计算耗时。通过nonlocal更新起始时间。属性附加:通过counter.count = count将状态暴露为属性。🖥️ 参考代码importtimefromfunctoolsimportwrapsdefmake_counter(initial:int=0):"""创建一个独立的计数器闭包。"""count=initialdefcounter():nonlocalcount count+=1returncount# 附加属性counter.count=count counter.reset=lambda:_reset()def_reset():nonlocalcount count=initial counter.count=countreturncounterdefmake_timer():"""创建一个计时器闭包。"""start_time=Noneelapsed_time=0deftimer():nonlocalstart_time,elapsed_time timer.start=lambda:_start()timer.stop=lambda:_stop()timer.elapsed=lambda:_elapsed()timer.reset=lambda:_reset()def_start():nonlocalstart_time start_time=time.perf_counter()def_stop():nonlocalelapsed_timeifstart_timeisnotNone:elapsed_time+=time.perf_counter()-start_time start_time=Nonedef_elapsed():nonlocalelapsed_time total=elapsed_timeifstart_timeisnotNone:total+=time.perf_counter()-start_timereturntotaldef_reset():nonlocalstart_time,elapsed_time start_time=Noneelapsed_time=0returntimerdefmake_averager():"""创建一个运行平均值计算器。"""numbers=[]total=0defaverager(value=None):nonlocaltotalifvalueisnotNone:numbers.append(value)total+=valuereturntotal/len(numbers)ifnumberselse0averager.values=lambda:list(numbers)averager.count=lambda:len(numbers)returnaverager# 测试if__name__=="__main__":print("=== 闭包计数器 ===")c1=make_counter()print(f"调用:{c1()}")# 1print(f"调用:{c1()}")# 2print(f"调用:{c1()}")# 3c2=make_counter(10)print(f"独立计数器:{c2()}")# 11print(f"原计数器:{c1()}")# 4c1.reset()print(f"重置后:{c1()}")# 1print("\n=== 闭包计时器 ===")t=make_timer()t.start()time.sleep(0.05)print(f"阶段1耗时:{t.elapsed():.4f}s")t.stop()time.sleep(0.05)t.start()time.sleep(0.05)t.stop()print(f"总耗时:{t.elapsed():.4f}s")t.reset()print(f"重置后:{t.elapsed():.4f}s")print("\n=== 运行平均值 ===")avg=make_averager()avg(10)avg(20)avg(30)print(f"平均值:{avg():.1f}")print(f"数据量:{avg.count()}")print("\n所有测试通过 ✓")🔗 关联知识点知识点说明嵌套函数函数内定义函数nonlocal声明使用外部非全局变量闭包独立实例每次调用创建独立作用域__closure__查看闭包引用的变量属性附加为闭包函数添加属性题目二:日志记录装饰器 ⭐⭐📌 题目描述实现一个通用的日志记录装饰器,记录函数调用信息:@log_calldefadd(a,b):returna+b add(3,5)# [CALL] add(3, 5)# [RETURN] add → 8@log_call(level="DEBUG")defgreet(name,greeting="Hello"):returnf"{greeting},{name}!"greet("Alice",greeting="Hi")# [DEBUG] greet(name='Alice', greeting='Hi')# [DEBUG] greet → 'Hi, Alice!'# 验证元信息保留print(add.__name__)# add(不是 wrapper)print(greet.__doc__)# 原文档字符串💡 编程思路这道题考察基础装饰器 +@wraps+*args/**kwargs:wrapper(*args, **kwargs):接收任意参数,保证装饰器的通用性。@wraps(func):保留原函数的__name__、__doc__等元信息。日志格式:记录调用时间、函数名、参数和返回值。带参数装饰器:三层嵌套,外层接收level等配置。🖥️ 参考代码importtimeimportfunctoolsfromdatetimeimportdatetimedeflog_call(func):"""记录函数调用信息的装饰器。"""@functools.wraps(func)defwrapper(*args,**kwargs):# 构建参数字符串args_str=", ".join(repr(a)forainargs)kwargs_str=", ".join(f"{k}={v!r}"fork,vinkwargs.items())all_args=", ".join(filter(None,[args_str,kwargs_str]))print(f"[CALL]{func.__name__}({all_args})")start=time.perf_counter()result=func(*args,**kwargs)elapsed=time.perf_counter()-startprint(f"[RETURN]{func.__name__}→{result!r}({elapsed:.6f}s)")returnresultreturnwrapperdeflog_with_level(level="INFO"):"""带日志级别的装饰器工厂。"""defdecorator(func):@functools.wraps(func)defwrapper(*args,**kwargs):args_str=", ".join(f"{a!r}"forainargs)+(", "ifkwargselse"")+", ".join(f"{k}={v!r}"fork,vinkwargs.items())ts=datetime.now().strftime("%H:%M:%S")print(f"[{level}]{ts}{func.__name__}({args_str})")result=func(*args,**kwargs)print(f"[{level}]{ts}{func.__name__}→{result!r}")returnresultreturnwrapperreturndecoratordeflog_to_file(filepath:str):"""将日志写入文件的装饰器。"""defdecorator(func):@functools.wraps(func)defwrapper(*args,**kwargs):args_str=", ".join(f"{a!r}"forainargs)kwargs_str=", ".join(f"{k}={v!r}"fork,vinkwargs.items())all_args=", ".join(filter(None,[args_str,kwargs_str]))result=func(*args,**kwargs)log_line=(f"[{datetime.now().isoformat()}] "f"{func.__name__}({all_args}) →{result!r}\n")withopen(filepath,"a",encoding="utf-8")asf:f.write(log_line)returnresultreturnwrapperreturndecorator# 测试if__name__=="__main__":print("=== log_call ===")
Python 装饰器专项练习:6 道编程题从入门到精通
配套专栏:Python 全栈修炼之路 第 14 篇《装饰器——Python 最优雅的语法糖》难度分布:⭐ → ⭐⭐ → ⭐⭐ → ⭐⭐⭐ → ⭐⭐⭐ → ⭐⭐⭐⭐核心覆盖:闭包、@wraps、带参数装饰器、类装饰器、多层装饰器叠加、缓存/重试/权限实战前言装饰器是 Python 中最强大的语法特性之一,它让我们在不修改原函数代码的前提下,优雅地为函数添加额外功能。本练习精选 6 道编程题,从闭包基础到综合实战,帮你彻底掌握装饰器的精髓。题目一:闭包计数器与计时器 ⭐📌 题目描述利用闭包实现一个函数调用计数器和一个简易计时器:# 闭包计数器counter=make_counter()print(counter())# 1print(counter())# 2print(counter())# 3print(counter.count)# 3(查看当前计数)# 每个计数器独立counter_b=make_counter()print(counter_b())# 1print(counter())# 4(互不影响)# 闭包计时器timer=make_timer()timer.start()time.sleep(0.1)print(timer.elapsed())# ≈ 0.1 秒💡 编程思路这道题考察闭包三大要素:嵌套函数、引用外部变量、返回内部函数。make_counter:外层函数创建count变量,内层函数通过nonlocal修改它。每次调用make_counter()创建独立的闭包实例。make_timer:外层函数创建start_time变量,内层函数读取它计算耗时。通过nonlocal更新起始时间。属性附加:通过counter.count = count将状态暴露为属性。🖥️ 参考代码importtimefromfunctoolsimportwrapsdefmake_counter(initial:int=0):"""创建一个独立的计数器闭包。"""count=initialdefcounter():nonlocalcount count+=1returncount# 附加属性counter.count=count counter.reset=lambda:_reset()def_reset():nonlocalcount count=initial counter.count=countreturncounterdefmake_timer():"""创建一个计时器闭包。"""start_time=Noneelapsed_time=0deftimer():nonlocalstart_time,elapsed_time timer.start=lambda:_start()timer.stop=lambda:_stop()timer.elapsed=lambda:_elapsed()timer.reset=lambda:_reset()def_start():nonlocalstart_time start_time=time.perf_counter()def_stop():nonlocalelapsed_timeifstart_timeisnotNone:elapsed_time+=time.perf_counter()-start_time start_time=Nonedef_elapsed():nonlocalelapsed_time total=elapsed_timeifstart_timeisnotNone:total+=time.perf_counter()-start_timereturntotaldef_reset():nonlocalstart_time,elapsed_time start_time=Noneelapsed_time=0returntimerdefmake_averager():"""创建一个运行平均值计算器。"""numbers=[]total=0defaverager(value=None):nonlocaltotalifvalueisnotNone:numbers.append(value)total+=valuereturntotal/len(numbers)ifnumberselse0averager.values=lambda:list(numbers)averager.count=lambda:len(numbers)returnaverager# 测试if__name__=="__main__":print("=== 闭包计数器 ===")c1=make_counter()print(f"调用:{c1()}")# 1print(f"调用:{c1()}")# 2print(f"调用:{c1()}")# 3c2=make_counter(10)print(f"独立计数器:{c2()}")# 11print(f"原计数器:{c1()}")# 4c1.reset()print(f"重置后:{c1()}")# 1print("\n=== 闭包计时器 ===")t=make_timer()t.start()time.sleep(0.05)print(f"阶段1耗时:{t.elapsed():.4f}s")t.stop()time.sleep(0.05)t.start()time.sleep(0.05)t.stop()print(f"总耗时:{t.elapsed():.4f}s")t.reset()print(f"重置后:{t.elapsed():.4f}s")print("\n=== 运行平均值 ===")avg=make_averager()avg(10)avg(20)avg(30)print(f"平均值:{avg():.1f}")print(f"数据量:{avg.count()}")print("\n所有测试通过 ✓")🔗 关联知识点知识点说明嵌套函数函数内定义函数nonlocal声明使用外部非全局变量闭包独立实例每次调用创建独立作用域__closure__查看闭包引用的变量属性附加为闭包函数添加属性题目二:日志记录装饰器 ⭐⭐📌 题目描述实现一个通用的日志记录装饰器,记录函数调用信息:@log_calldefadd(a,b):returna+b add(3,5)# [CALL] add(3, 5)# [RETURN] add → 8@log_call(level="DEBUG")defgreet(name,greeting="Hello"):returnf"{greeting},{name}!"greet("Alice",greeting="Hi")# [DEBUG] greet(name='Alice', greeting='Hi')# [DEBUG] greet → 'Hi, Alice!'# 验证元信息保留print(add.__name__)# add(不是 wrapper)print(greet.__doc__)# 原文档字符串💡 编程思路这道题考察基础装饰器 +@wraps+*args/**kwargs:wrapper(*args, **kwargs):接收任意参数,保证装饰器的通用性。@wraps(func):保留原函数的__name__、__doc__等元信息。日志格式:记录调用时间、函数名、参数和返回值。带参数装饰器:三层嵌套,外层接收level等配置。🖥️ 参考代码importtimeimportfunctoolsfromdatetimeimportdatetimedeflog_call(func):"""记录函数调用信息的装饰器。"""@functools.wraps(func)defwrapper(*args,**kwargs):# 构建参数字符串args_str=", ".join(repr(a)forainargs)kwargs_str=", ".join(f"{k}={v!r}"fork,vinkwargs.items())all_args=", ".join(filter(None,[args_str,kwargs_str]))print(f"[CALL]{func.__name__}({all_args})")start=time.perf_counter()result=func(*args,**kwargs)elapsed=time.perf_counter()-startprint(f"[RETURN]{func.__name__}→{result!r}({elapsed:.6f}s)")returnresultreturnwrapperdeflog_with_level(level="INFO"):"""带日志级别的装饰器工厂。"""defdecorator(func):@functools.wraps(func)defwrapper(*args,**kwargs):args_str=", ".join(f"{a!r}"forainargs)+(", "ifkwargselse"")+", ".join(f"{k}={v!r}"fork,vinkwargs.items())ts=datetime.now().strftime("%H:%M:%S")print(f"[{level}]{ts}{func.__name__}({args_str})")result=func(*args,**kwargs)print(f"[{level}]{ts}{func.__name__}→{result!r}")returnresultreturnwrapperreturndecoratordeflog_to_file(filepath:str):"""将日志写入文件的装饰器。"""defdecorator(func):@functools.wraps(func)defwrapper(*args,**kwargs):args_str=", ".join(f"{a!r}"forainargs)kwargs_str=", ".join(f"{k}={v!r}"fork,vinkwargs.items())all_args=", ".join(filter(None,[args_str,kwargs_str]))result=func(*args,**kwargs)log_line=(f"[{datetime.now().isoformat()}] "f"{func.__name__}({all_args}) →{result!r}\n")withopen(filepath,"a",encoding="utf-8")asf:f.write(log_line)returnresultreturnwrapperreturndecorator# 测试if__name__=="__main__":print("=== log_call ===")