news 2026/7/8 1:59:33

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【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 进阶的必经之路,它们不仅让代码更简洁,也体现了函数式编程的优雅。希望本文能帮助你从原理到实战全面掌握这一重要特性。如果你有任何疑问或想深入探讨某个细节,欢迎在评论区留言交流!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/8 1:59:14

一篇看懂 Linux 终端常用命令:从零掌握增删改查与权限管理

一篇看懂 Linux 终端常用命令:从零掌握增删改查与权限管理 很多同学在 Linux 系统里部署好了各种工具,平时也能用"大白话"的方式去开发项目。但一旦遇到一些特殊环境——比如单片机、各种微型 Linux 设备,图形界面往往是奢望,我们就必须回到终端,用最朴素的增删改查命…

作者头像 李华
网站建设 2026/7/8 1:57:47

加了Query改写,准确率从71%提到89%

图1 Query改写&#xff1a;把客户说的话&#xff0c;翻译成搜索引擎能理解的话 私信「改写」获取 Query改写Prompt模板 意图词典JSON 有一个客户问题&#xff0c;我记到现在。 “这个保险能退吗&#xff1f;” 把这句话原样扔进知识库搜索。你猜搜出来什么&#xff1f; 退…

作者头像 李华
网站建设 2026/7/8 1:57:22

MongoDB-集合(表)关联查询

MongoDB 可以做集合&#xff08;表&#xff09;关联&#xff0c;但和 MySQL JOIN 完全不一样一、核心结论MongoDB 支持跨集合关联查询&#xff0c;靠聚合管道 $lookup 实现&#xff0c;等价 SQL 左外连接 LEFT JOINMongoDB。限制&#xff1a;只能同库关联&#xff0c;不支持跨库…

作者头像 李华
网站建设 2026/7/8 1:56:31

音视频编解码:VP8 / VP9 / H.264 / Opus

音视频编解码&#xff1a;VP8 / VP9 / H.264 / Opus WebRTC 支持哪些音视频编解码器&#xff1f;各有什么优缺点&#xff1f;怎么选&#xff1f;码率怎么设&#xff1f;这篇讲 WebRTC 常用的视频编码&#xff08;VP8/VP9/H.264/AV1&#xff09;和音频编码&#xff08;Opus/G.71…

作者头像 李华