【python零基础教程第10讲】Python 闭包与装饰器 Python 闭包与装饰器从嵌套函数到实战应用在 Python 中闭包与装饰器是函数式编程的核心特性它们让代码更加优雅、可复用并且能够在不修改原函数代码的前提下增强其功能。本文将从嵌套函数出发逐步深入闭包的概念、自由变量的作用再到装饰器的各种形态无参、带参、多层叠加最后通过functools.wraps保留原函数信息并给出计时、日志、权限校验三个实战案例。无论你是初学者还是进阶开发者都能从中获得扎实的理解与实用的技巧。一、嵌套函数函数内部的函数Python 允许在函数内部定义另一个函数这种结构称为嵌套函数。内部函数可以访问外部函数的变量但外部函数不能直接访问内部函数的局部变量。defouter():x10definner():print(f内部函数访问外部变量 x {x})inner()outer()# 输出: 内部函数访问外部变量 x 10嵌套函数的作用域规则遵循LEGBLocal, Enclosing, Global, Built-in原则。内部函数可以读取外部函数的变量但不能直接修改除非使用nonlocal关键字。defouter():count0definner():nonlocalcount# 声明使用外部函数的变量count1returncountreturninner counterouter()print(counter())# 1print(counter())# 2嵌套函数是闭包和装饰器的基础理解它的作用域机制至关重要。二、闭包概念函数 环境闭包Closure是指一个函数内部函数连同其引用的外部变量自由变量所构成的整体。即使外部函数已经执行完毕内部函数仍然可以记住并访问那些自由变量。defmake_multiplier(factor):defmultiply(number):returnnumber*factorreturnmultiply doublemake_multiplier(2)triplemake_multiplier(3)print(double(5))# 10print(triple(5))# 15这里multiply引用了外部变量factor当make_multiplier返回后factor的值被保留在闭包中。double和triple是两个不同的闭包实例各自持有不同的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_hellomy_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(调用前)resultfunc(*args,**kwargs)print(调用后)returnresultreturnwrappermy_decoratordefadd(a,b):returnabprint(add(3,5))# 输出: 调用前 调用后 8四、带参数装饰器有时装饰器本身需要参数例如指定日志级别、权限角色等。这时需要在装饰器外层再嵌套一层函数形成三层结构defrepeat(n):defdecorator(func):defwrapper(*args,**kwargs):for_inrange(n):resultfunc(*args,**kwargs)returnresultreturnwrapperreturndecoratorrepeat(3)defgreet(name):print(fHello,{name}!)greet(Alice)# 输出三遍: Hello, Alice!执行流程repeat(3)返回decorator函数。decorator将greet作为参数传入decorator返回wrapper。调用greet(Alice)实际执行wrapper。带参数装饰器的通用模板defdecorator_with_args(arg1,arg2):defactual_decorator(func):defwrapper(*args,**kwargs):# 使用 arg1, arg2 和 funcreturnfunc(*args,**kwargs)returnwrapperreturnactual_decorator五、多层装饰器叠加多个装饰器可以叠加使用执行顺序从下往上离函数定义最近的装饰器先应用但实际调用时从外到内。defbold(func):defwrapper():returnbfunc()/breturnwrapperdefitalic(func):defwrapper():returnifunc()/ireturnwrapperbolditalicdefhello():returnHelloprint(hello())# biHello/i/b等价于hello bold(italic(hello))。先应用italic再应用bold。调用时bold的wrapper先执行内部调用italic的wrapper最后调用原函数。多层装饰器常用于组合多个横切关注点如日志 权限 缓存。六、functools.wraps保留原函数信息装饰器返回的wrapper函数会覆盖原函数的元信息如__name__、__doc__、__module__等。使用functools.wraps可以将原函数的属性复制到wrapper上。importfunctoolsdefmy_decorator(func):functools.wraps(func)defwrapper(*args,**kwargs):这是 wrapper 的文档print(调用前)returnfunc(*args,**kwargs)returnwrappermy_decoratordefadd(a,b):返回 a 和 b 的和returnabprint(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):starttime.perf_counter()resultfunc(*args,**kwargs)endtime.perf_counter()print(f{func.__name__}执行耗时:{end-start:.6f}秒)returnresultreturnwrappertimerdefslow_function():time.sleep(0.5)return完成slow_function()# 输出: slow_function 执行耗时: 0.500123 秒7.2 日志装饰器记录函数调用信息包括参数和返回值便于调试和审计。importfunctoolsimportlogging logging.basicConfig(levellogging.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_reprkwargs_repr)logging.info(f调用{func.__name__}({signature}))resultfunc(*args,**kwargs)logging.info(f{func.__name__}返回{result!r})returnresultreturnwrapperlogdefdivide(a,b):returna/b divide(10,2)# 日志: 调用 divide(10, 2) ; divide 返回 5.07.3 权限校验装饰器模拟用户角色检查常用于 Web 框架中的访问控制。importfunctoolsdefrequire_role(role):defdecorator(func):functools.wraps(func)defwrapper(*args,**kwargs):# 假设当前用户信息从全局或参数中获取current_userget_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 进阶的必经之路它们不仅让代码更简洁也体现了函数式编程的优雅。希望本文能帮助你从原理到实战全面掌握这一重要特性。如果你有任何疑问或想深入探讨某个细节欢迎在评论区留言交流