查看: 111|回复: 3

Python闭包与装饰器零基础到实战:从函数嵌套到三大企业级装饰器

[复制链接]
发表于 1 小时前 | 显示全部楼层 |阅读模式
闭包和装饰器是Python进阶必须掌握的核心语法,也是理解Flask、Django等框架源码、实现日志、鉴权、缓存等功能的基石。很多开发者学了许久仍无法手写装饰器,根本原因在于没吃透闭包原理。本文从函数作为一等公民讲起,逐步拆解闭包三要素、装饰器本质、带参装饰器、多层装饰器执行顺序,最后给出三个可直接复用的企业级装饰器。所有代码均经过测试,可放心使用。

一、前置基础:函数是一等公民

Python中函数是一等对象,这意味着:
1. 函数可以赋值给变量
2. 函数可以作为参数或返回值
3. 函数可以嵌套定义

这种特性是闭包和装饰器成立的底层基础。例如:
  1. def outer():
  2.     def inner():
  3.         print("内部函数执行")
  4.     return inner
  5. res = outer()
  6. res()
复制代码
正是由于函数可以被嵌套并且作为返回值,才诞生了闭包这种结构。

二、闭包详解:定义、条件与原理

2.1 闭包的三大必要条件
同时满足以下三点即为闭包:
- 函数嵌套(外层函数 + 内层函数)
- 内层函数引用外层函数的局部变量
- 外层函数返回内层函数

2.2 闭包实战代码
  1. def outer(num):
  2.     def inner():
  3.         print(f"传入数值:{num}")
  4.     return inner
  5. func = outer(100)
  6. func()
复制代码

2.3 闭包核心特性:变量常驻
普通函数执行结束后,局部变量会被销毁。但闭包会保留外层函数的局部变量不被GC回收,从而实现状态保存和数据常驻。

2.4 经典场景:计数器
  1. def count_factory():
  2.     cnt = 0
  3.     def counter():
  4.         nonlocal cnt
  5.         cnt += 1
  6.         print(f"当前计数:{cnt}")
  7.     return counter
  8. c1 = count_factory()
  9. c1()  # 输出:当前计数:1
  10. c1()  # 输出:当前计数:2
  11. c1()  # 输出:当前计数:3
复制代码
需注意nonlocal关键字用于在内层函数中修改外层函数的局部变量。

三、装饰器本质:基于闭包的语法糖

装饰器 = 高阶闭包 + @语法糖。核心作用:在不修改原函数代码、不改变调用方式的前提下,给函数新增功能,符合“开闭原则”(对扩展开放,对修改关闭)。

装饰器解决的问题包括:
- 避免重复代码(日志、计时、鉴权等通用逻辑复用)
- 业务代码与通用工具代码解耦
- 统一项目规范,便于维护

四、手写无参装饰器:日志统计实战

4.1 原生闭包写法(理解底层)
  1. import time
  2. def timer_decorator(func):
  3.     def wrapper(*args, **kwargs):
  4.         start = time.time()
  5.         res = func(*args, **kwargs)
  6.         end = time.time()
  7.         print(f"函数【{func.__name__}】执行耗时:{end - start:.4f}s")
  8.         return res
  9.     return wrapper
  10. @timer_decorator
  11. def calc_sum(n):
  12.     s = 0
  13.     for i in range(n):
  14.         s += i
  15.     return s
  16. calc_sum(100000)
复制代码

4.2 装饰器的执行流程
@timer_decorator 等价于 calc_sum = timer_decorator(calc_sum)。调用 calc_sum() 实际执行 wrapper():先运行前置逻辑(记录开始时间),然后执行原函数,再运行后置逻辑(计算耗时),最后返回原函数结果。

4.3 解决函数信息被覆盖的问题
直接装饰会使原函数名、文档字符串被wrapper覆盖。使用 functools.wraps 修复:
  1. from functools import wraps
  2. def timer_decorator(func):
  3.     @wraps(func)
  4.     def wrapper(*args, **kwargs):
  5.         start = time.time()
  6.         res = func(*args, **kwargs)
  7.         end = time.time()
  8.         print(f"函数【{func.__name__}】执行耗时:{end - start:.4f}s")
  9.         return res
  10.     return wrapper
复制代码

五、进阶:带参数装饰器 + 多层装饰器

5.1 带参数装饰器
普通装饰器只能接收函数作为参数。带参装饰器可以自定义配置参数,实现功能开关等。例如实现一个可以控制是否输出日志的装饰器:
  1. from functools import wraps
  2. def log_decorator(switch=True):
  3.     def decorator(func):
  4.         @wraps(func)
  5.         def wrapper(*args, **kwargs):
  6.             if switch:
  7.                 print(f"【日志】函数{func.__name__}开始执行")
  8.             return func(*args, **kwargs)
  9.         return wrapper
  10.     return decorator
  11. @log_decorator(switch=True)
  12. def test_func():
  13.     print("业务逻辑执行")
  14. test_func()
复制代码

5.2 多层装饰器叠加执行顺序
加载顺序:自下而上(离函数最近的装饰器先加载)。执行顺序:自上而下(先执行最外层装饰器的前置逻辑)。口诀:先装饰的后执行,后装饰的先执行。
  1. from functools import wraps
  2. def deco1(func):
  3.     @wraps(func)
  4.     def wrapper():
  5.         print("deco1 前置")
  6.         func()
  7.         print("deco1 后置")
  8.     return wrapper
  9. def deco2(func):
  10.     @wraps(func)
  11.     def wrapper():
  12.         print("deco2 前置")
  13.         func()
  14.         print("deco2 后置")
  15.     return wrapper
  16. @deco1
  17. @deco2
  18. def hello():
  19.     print("核心业务执行")
  20. hello()
  21. # 输出顺序:
  22. # deco1 前置
  23. # deco2 前置
  24. # 核心业务执行
  25. # deco2 后置
  26. # deco1 后置
复制代码

六、企业级实战三大装饰器(可直接复用)

6.1 接口鉴权装饰器
  1. from functools import wraps
  2. def login_auth(func):
  3.     @wraps(func)
  4.     def wrapper(user_token, *args, **kwargs):
  5.         if not user_token:
  6.             return {"code": 401, "msg": "未登录,权限不足"}
  7.         return func(user_token, *args, **kwargs)
  8.     return wrapper
  9. @login_auth
  10. def get_user_info(token):
  11.     return {"code": 200, "data": "用户信息数据"}
  12. print(get_user_info(""))  # 返回401
  13. print(get_user_info("valid_token"))  # 返回200
复制代码

6.2 简易缓存装饰器(避免重复计算)
  1. from functools import wraps
  2. def cache_decorator(func):
  3.     cache = {}
  4.     @wraps(func)
  5.     def wrapper(*args):
  6.         if args not in cache:
  7.             cache[args] = func(*args)
  8.         return cache[args]
  9.     return wrapper
  10. @cache_decorator
  11. def fib(n):
  12.     if n <= 2:
  13.         return 1
  14.     return fib(n-1) + fib(n-2)
  15. print(fib(30))  # 首次计算递归后缓存结果,后续调用直接读取缓存
复制代码
此装饰器以函数参数为键存储结果,适合纯函数(相同输入永远得到相同输出)。

6.3 异常捕获装饰器(统一异常处理)
  1. from functools import wraps
  2. def exception_catch(func):
  3.     @wraps(func)
  4.     def wrapper(*args, **kwargs):
  5.         try:
  6.             return func(*args, **kwargs)
  7.         except Exception as e:
  8.             print(f"【函数异常】{func.__name__}执行失败:{str(e)}")
  9.             return None
  10.     return wrapper
  11. @exception_catch
  12. def divide(a, b):
  13.     return a / b
  14. print(divide(10, 0))  # 输出异常信息并返回None
复制代码

七、面试高频真题整理

Q1:什么是闭包?闭包的作用和缺点?
闭包是一种嵌套函数结构:外层函数返回内层函数,且内层函数引用了外层函数的局部变量。作用:保存函数执行状态、实现数据常驻、简化代码、实现装饰器。缺点:外层变量常驻内存不释放,长时间使用易导致内存泄漏。

Q2:装饰器的原理是什么?为什么不修改原代码?
装饰器基于闭包和高阶函数实现,通过包装原函数在调用前后扩展逻辑。符合开闭原则,使得业务逻辑与通用逻辑解耦,无需修改原函数内部实现。

Q3:wraps装饰器的作用?
functools.wraps 用于修复因装饰器导致的原函数信息丢失问题,保留原函数的 __name__、__doc__ 等属性。

Q4:多层装饰器的执行顺序?
加载顺序(装饰器声明顺序)自下而上,执行顺序自上而下。即最靠近函数的装饰器先被装饰,但执行时其前置逻辑最后执行。

通过以上系统讲解,闭包与装饰器的原理、手写方法、进阶用法及企业级实战已清晰呈现。建议读者动手运行每一个代码示例,加深理解。
回复

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Python闭包与装饰器零基础到实战:从函数嵌套到三大企业级装饰器

楼主的教程写得非常清晰,从闭包原理到装饰器实战,逻辑层层递进,尤其喜欢“先装饰的后执行,后装饰的先执行”这个口诀,一下就把多层装饰器的执行顺序记住了。三个企业级装饰器也很实用,正好最近项目里要用日志和鉴权,直接参考代码能省不少排查时间。感谢分享!
回复 支持 反对

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Python闭包与装饰器零基础到实战:从函数嵌套到三大企业级装饰器

非常感谢楼主的详细分享!从闭包的三条件到装饰器的语法糖,再到带参装饰器和多层执行顺序,逻辑很清晰,代码也都能直接跑通,对初学者理解底层原理特别有帮助。尤其是用 `nonlocal` 修改外层变量的计数器示例,把“变量常驻”这个特性讲得明明白白。另外 `functools.wraps` 的强调也很实用,很多人刚学时容易忽略这点。期待后面三个企业级装饰器的完整代码,方便的话能不能先透露下大概的应用场景?比如日志、鉴权和缓存吗?再次感谢!
回复 支持 反对

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Python闭包与装饰器零基础到实战:从函数嵌套到三大企业级装饰器

非常感谢楼主这么详细的讲解!我一直对闭包的变量常驻机制有点模糊,看到计数器那个例子和 `nonlocal` 的用法瞬间就通了。装饰器部分把 `@wraps` 和多层执行顺序讲得特别清楚,之前踩过函数名被覆盖的坑,现在终于知道怎么修复了。想问一下,在实际企业项目里,日志、鉴权这类装饰器会写成类装饰器吗?还是像你给的这种函数嵌套写法就够用了?期待后续的三大企业级装饰器实战!
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-6-28 12:54 , Processed in 0.039788 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部