当前位置: 首页 > news >正文

【python零基础教程第10讲】Python 闭包与装饰器

Python 闭包与装饰器:从嵌套函数到实战应用

在 Python 中,闭包与装饰器是函数式编程的核心特性,它们让代码更加优雅、可复用,并且能够在不修改原函数代码的前提下增强其功能。本文将从嵌套函数出发,逐步深入闭包的概念、自由变量的作用,再到装饰器的各种形态(无参、带参、多层叠加),最后通过functools.wraps保留原函数信息,并给出计时、日志、权限校验三个实战案例。无论你是初学者还是进阶开发者,都能从中获得扎实的理解与实用的技巧。


一、嵌套函数:函数内部的函数

Python 允许在函数内部定义另一个函数,这种结构称为嵌套函数。内部函数可以访问外部函数的变量,但外部函数不能直接访问内部函数的局部变量。

defouter():x=10definner():print(f"内部函数访问外部变量 x ={x}")inner()outer()# 输出: 内部函数访问外部变量 x = 10

嵌套函数的作用域规则遵循LEGB(Local, Enclosing, Global, Built-in)原则。内部函数可以读取外部函数的变量,但不能直接修改(除非使用nonlocal关键字)。

defouter():count=0definner():nonlocalcount# 声明使用外部函数的变量count+=1returncountreturninner counter=outer()print(counter())# 1print(counter())# 2

嵌套函数是闭包和装饰器的基础,理解它的作用域机制至关重要。


二、闭包概念:函数 + 环境

闭包(Closure)是指一个函数(内部函数)连同其引用的外部变量(自由变量)所构成的整体。即使外部函数已经执行完毕,内部函数仍然可以记住并访问那些自由变量。

defmake_multiplier(factor):defmultiply(number):returnnumber*factorreturnmultiply double=make_multiplier(2)triple=make_multiplier(3)print(double(5))# 10print(triple(5))# 15

这里multiply引用了外部变量factor,当make_multiplier返回后,factor的值被保留在闭包中。doubletriple是两个不同的闭包实例,各自持有不同的factor

自由变量

自由变量是指在嵌套函数中引用的、但既不是该函数的局部变量也不是全局变量的变量。在上例中,factor就是multiply的自由变量。Python 通过__closure__属性可以查看闭包中绑定的自由变量:

print(double.__closure__.cell_contents)# 2print(triple.__closure__.cell_contents)# 3

闭包常用于:

  • 数据隐藏(模拟私有变量)
  • 延迟计算
  • 回调函数
  • 装饰器

三、装饰器基础:无参装饰器

装饰器本质上是一个接受函数作为参数并返回新函数的可调用对象(通常是函数)。它允许在不修改原函数代码的情况下,动态地给函数添加功能。

3.1 最简单的装饰器

defmy_decorator(func):defwrapper():print("在函数调用前执行")func()print("在函数调用后执行")returnwrapperdefsay_hello():print("Hello!")say_hello=my_decorator(say_hello)say_hello()

输出:

在函数调用前执行 Hello! 在函数调用后执行

3.2 使用 @ 语法糖

Python 提供了@语法糖,使装饰器的应用更加简洁:

@my_decoratordefsay_hello():print("Hello!")say_hello()

@my_decorator等价于say_hello = my_decorator(say_hello)

3.3 装饰带参数的函数

如果原函数需要参数,wrapper必须使用*args**kwargs来接收任意参数,并传递给原函数:

defmy_decorator(func):defwrapper(*args,**kwargs):print("调用前")result=func(*args,**kwargs)print("调用后")returnresultreturnwrapper@my_decoratordefadd(a,b):returna+bprint(add(3,5))# 输出: 调用前 调用后 8

四、带参数装饰器

有时装饰器本身需要参数(例如指定日志级别、权限角色等)。这时需要在装饰器外层再嵌套一层函数,形成三层结构

defrepeat(n):defdecorator(func):defwrapper(*args,**kwargs):for_inrange(n):result=func(*args,**kwargs)returnresultreturnwrapperreturndecorator@repeat(3)defgreet(name):print(f"Hello,{name}!")greet("Alice")# 输出三遍: Hello, Alice!

执行流程:

  1. repeat(3)返回decorator函数。
  2. @decoratorgreet作为参数传入decorator,返回wrapper
  3. 调用greet("Alice")实际执行wrapper

带参数装饰器的通用模板:

defdecorator_with_args(arg1,arg2):defactual_decorator(func):defwrapper(*args,**kwargs):# 使用 arg1, arg2 和 funcreturnfunc(*args,**kwargs)returnwrapperreturnactual_decorator

五、多层装饰器叠加

多个装饰器可以叠加使用,执行顺序从下往上(离函数定义最近的装饰器先应用,但实际调用时从外到内)。

defbold(func):defwrapper():return"<b>"+func()+"</b>"returnwrapperdefitalic(func):defwrapper():return"<i>"+func()+"</i>"returnwrapper@bold@italicdefhello():return"Hello"print(hello())# <b><i>Hello</i></b>

等价于hello = bold(italic(hello))。先应用italic,再应用bold。调用时,boldwrapper先执行,内部调用italicwrapper,最后调用原函数。

多层装饰器常用于组合多个横切关注点,如日志 + 权限 + 缓存。


六、functools.wraps:保留原函数信息

装饰器返回的wrapper函数会覆盖原函数的元信息(如__name____doc____module__等)。使用functools.wraps可以将原函数的属性复制到wrapper上。

importfunctoolsdefmy_decorator(func):@functools.wraps(func)defwrapper(*args,**kwargs):"""这是 wrapper 的文档"""print("调用前")returnfunc(*args,**kwargs)returnwrapper@my_decoratordefadd(a,b):"""返回 a 和 b 的和"""returna+bprint(add.__name__)# add (而不是 wrapper)print(add.__doc__)# 返回 a 和 b 的和 (而不是 "这是 wrapper 的文档")

functools.wraps实际上是一个装饰器,它接受原函数作为参数,将__module____name____qualname____doc____dict____wrapped__等属性复制到wrapper上。建议在编写所有装饰器时都使用@functools.wraps(func),以保持函数签名和文档的完整性。


七、装饰器实战:计时、日志、权限校验

7.1 计时装饰器

用于测量函数执行时间,常用于性能分析。

importtimeimportfunctoolsdeftimer(func):@functools.wraps(func)defwrapper(*args,**kwargs):start=time.perf_counter()result=func(*args,**kwargs)end=time.perf_counter()print(f"{func.__name__}执行耗时:{end-start:.6f}秒")returnresultreturnwrapper@timerdefslow_function():time.sleep(0.5)return"完成"slow_function()# 输出: slow_function 执行耗时: 0.500123 秒

7.2 日志装饰器

记录函数调用信息,包括参数和返回值,便于调试和审计。

importfunctoolsimportlogging logging.basicConfig(level=logging.INFO)deflog(func):@functools.wraps(func)defwrapper(*args,**kwargs):args_repr=[repr(a)forainargs]kwargs_repr=[f"{k}={v!r}"fork,vinkwargs.items()]signature=", ".join(args_repr+kwargs_repr)logging.info(f"调用{func.__name__}({signature})")result=func(*args,**kwargs)logging.info(f"{func.__name__}返回{result!r}")returnresultreturnwrapper@logdefdivide(a,b):returna/b divide(10,2)# 日志: 调用 divide(10, 2) ; divide 返回 5.0

7.3 权限校验装饰器

模拟用户角色检查,常用于 Web 框架中的访问控制。

importfunctoolsdefrequire_role(role):defdecorator(func):@functools.wraps(func)defwrapper(*args,**kwargs):# 假设当前用户信息从全局或参数中获取current_user=get_current_user()# 伪代码ifcurrent_user.get("role")!=role:raisePermissionError(f"需要{role}权限,当前角色:{current_user.get('role')}")returnfunc(*args,**kwargs)returnwrapperreturndecorator# 使用示例@require_role("admin")defdelete_user(user_id):print(f"删除用户{user_id}")# 如果当前用户不是 admin,会抛出 PermissionError

实际项目中,权限校验通常结合会话、JWT 等机制,装饰器可以灵活地插入到任何需要权限控制的函数上。


八、总结与最佳实践

  • 闭包是装饰器的底层机制,理解自由变量和nonlocal是掌握装饰器的关键。
  • 装饰器提供了一种声明式编程风格,将横切关注点(如日志、计时、权限)与业务逻辑分离。
  • 带参数装饰器通过三层嵌套实现,外层接收装饰器参数,中层接收函数,内层接收原函数参数。
  • 多层装饰器从下往上应用,从上往下执行,组合时注意顺序。
  • 始终使用@functools.wraps来保留原函数的元信息,这是专业代码的必备习惯。
  • 实战中,装饰器可以嵌套使用,例如@log @timer同时记录日志和计时,但要注意执行顺序可能影响结果。

闭包与装饰器是 Python 进阶的必经之路,它们不仅让代码更简洁,也体现了函数式编程的优雅。希望本文能帮助你从原理到实战全面掌握这一重要特性。如果你有任何疑问或想深入探讨某个细节,欢迎在评论区留言交流!

http://www.cnnetsun.cn/news/3211803.html

相关文章:

  • 一篇看懂 Linux 终端常用命令:从零掌握增删改查与权限管理
  • 加了Query改写,准确率从71%提到89%
  • MongoDB-集合(表)关联查询
  • 【OpenHarmony/HarmonyOs 】化学学习 App 收藏夹功能实践:AppStorage 本地持久化与响应式刷新
  • 音视频编解码:VP8 / VP9 / H.264 / Opus
  • 2026降AIGC革命:AI率92%暴降至5%!实测10款AI智能降重工具!10款工具深度解析!
  • 3分钟搞定网易云插件安装:BetterNCM-Installer终极解决方案
  • spring通过方法注入,针对多实例的Bean
  • 2026年7月探寻四川信誉优良的GEO优化运营商
  • AutoJS6 + Shizuku 实战:淘金币自动签到与任务脚本
  • 如何一次性解决所有Visual C++运行库问题:终极AIO安装指南
  • Typeahead 2.0 智能搜索体验升级实战
  • 阿里面试:如果Agent 挂100个 Skill ,如何提升召回率、准确度、F1综合值?尼恩 《Skills调优圣经》,来一次 泰山压顶 式的 降维打击
  • rust 学习 闭包
  • 第4章:Linux系统重点复习
  • AI 看不懂老项目?先用“项目说明书 + 目录树”把上下文交给它
  • [特殊字符]《京东宙斯迁移公告解读:老接口 360buy.ware.get→ jingdong.ware.read.get平滑切换(附Python源码)》
  • 从零搭建工业物联网监控大屏:Node-RED + 有人云 零代码方案
  • HRD进化论04讲:战略解码
  • PostgreSQL高级特性应用
  • PyTorch DDP 多机多卡配置:2节点8卡集群部署与通信瓶颈分析
  • Models-学习指南_小白版
  • Mermaid终极指南:用代码快速创建专业图表,彻底告别文档滞后问题
  • Windows 证书管理:3种存储位置(个人/根/中间)的导入策略与安全影响
  • AD7175-8与PIC18F4610高精度信号采集方案解析
  • 2026年AI写作辅助平台实测认证:5款神器从文献到降重一站式避坑指南
  • 15个实用的代码审查工具,自动化检查
  • 如何快速精通猫抓资源嗅探:进阶用户终极配置指南
  • 15个实用的代码版本控制工具,Git替代品
  • 《B4071 [GESP202412 五级] 武器强化》