闭包 · 作用域 · 装饰器
一、闭包(Closure)
1. 定义
闭包 = 函数 + 它记住的外部变量。
一个函数记住了它定义时所在的环境(外部变量),即使外部函数已经执行完了,它还能用那些变量。
2. 最简单的例子
1 | def outer(): |
outer() 执行完后,message 本该消失,但 f() 还能打印出来——因为 inner 把它记住了。
3. 闭包的三个条件
- 有嵌套函数(函数里面定义函数)
- 内部函数用到了外部函数的变量
- 外部函数返回内部函数
4. 计数器例子
1 | def counter(): |
count 是私有的,外面访问不到。
5. 函数工厂例子
1 | def make_multiplier(n): |
6. 总结
闭包就是:函数把外部的变量”打包”带走了,走到哪用到哪。
常见用途:装饰器、计数器、函数工厂、数据隐藏。
二、作用域:locals() 和 globals()
1. 通俗理解
locals():查看当前房间里有什么(局部作用域)globals():查看整栋大楼里有什么(全局作用域)
它们都返回一个字典。
2. locals() 例子
1 | def my_room(): |
在函数外面调用 locals(),其实就等于 globals()。
3. globals() 例子
1 | score = 99 # 全局变量 |
4. 对比表
| 特性 | locals() |
globals() |
|---|---|---|
| 作用范围 | 当前所在的局部空间 | 永远是整个程序的全局空间 |
| 返回内容 | 当前函数内的变量 | 全局变量 + 内置变量 |
| 在哪里用最准 | 函数内部 | 任何地方 |
| 能不能修改 | 通常只读 | 可以修改 |
5. 陷阱:能不能改?
globals() 可以改:
1 | x = 10 |
locals() 改了通常没用(在函数里):
1 | def test(): |
6. 实际用途
1 | # 用途一:调试打印 |
7. 总结
locals()看自己房间,globals()看整栋大楼。globals()能改全局,locals()通常只能看不能改。
三、装饰器(Decorator)
1. 定义
装饰器 = 一个接收函数、返回新函数的函数。
它让你不碰原函数,就能给它套上”日志、计时、权限、缓存”等外套。
2. 生活类比
- 白T恤(原函数)→ 套一件印好图案的外套(装饰器)→ 原T恤没动,效果变了。
3. 最简单的例子
1 | def my_decorator(func): |
输出:
1 | === 开始执行 === |
4. 装饰器模板
1 | def 装饰器名(func): |
5. 装饰器的作用
| 功能 | 说明 |
|---|---|
| 日志记录 | 每次调用自动打印日志 |
| 性能计时 | 自动统计函数运行时间 |
| 权限校验 | 调用前检查用户是否登录 |
| 缓存结果 | 相同参数直接返回缓存 |
| 重试机制 | 失败自动重试 N 次 |
| 参数校验 | 检查参数是否合法 |
6. 实战例子:计时器
1 | import time |
7. 实战例子:权限校验
1 | def login_required(func): |
逐行解释:
login_required(func):定义一个”门卫制造机”。wrapper:包裹层,先检查登录,没登录就直接return(拦截),登录了才放行。@login_required等价于view_profile = login_required(view_profile)。- 调用
view_profile()时,实际执行的是wrapper()。
8. 实战例子:带参数的重试装饰器
1 | def retry(times): |
输出:
1 | 第1次失败:网络错误 |
关键理解:
- 三层结构:
retry(收参数)→decorator(收函数)→wrapper(干活)。 @retry(times=3)等价于:1
2temp = retry(times=3)
unstable = temp(unstable)return func(...)成功时立刻返回,循环结束;失败则被except抓住,继续下一轮。- 循环全失败后才打印”全部重试失败”。
9. 使用场景
| 场景 | 说明 |
|---|---|
| 多个函数有相同的”前后处理” | 记日志、计时、校验 |
| 框架开发 | Flask 的 @app.route()、Django 的 @login_required |
| 不想改原代码 | 原代码可能是别人写的 |
| AOP(面向切面编程) | 抽出日志、权限、缓存等横切关注点 |
10. 两个必知坑
坑1:原函数名字变了
1 | print(say_hello.__name__) # wrapper(不是 say_hello) |
解决:用 functools.wraps。
坑2:多个装饰器执行顺序
1 |
|
执行顺序:从下往上,先 b 再 a。
11. 总结
装饰器 = 接收函数、返回新函数。
@一放,代码干净;原函数不动,功能增强。
速记口诀:
传入函数,返回函数;中间夹心,增强功能;
@一放,代码干净。
四、装饰器修复技术:functools.wraps
1. 问题:装饰器会偷走原函数身份
1 | def my_decorator(func): |
原因:@my_decorator 等价于 say_hello = my_decorator(say_hello),返回的是 wrapper。
2. 解决:@functools.wraps
1 | from functools import wraps |
3. 复制了哪些属性
| 属性 | 含义 |
|---|---|
__name__ |
函数名 |
__doc__ |
文档字符串 |
__module__ |
所属模块 |
__qualname__ |
限定名 |
__dict__ |
自定义属性 |
__wrapped__ |
指向原函数 |
4. 使用模板
1 | from functools import wraps |
带参数的装饰器加在最内层:
1 | def retry(times): |
5. 总结
装饰器会偷走原函数身份,
@wraps(func)是身份恢复器。
速记口诀:
装饰器一套,名字就丢掉;加个
@wraps,身份全找回。
五、语法糖(有参、无参)
1. 什么是语法糖
语法糖 = 让代码更好看、更好写的”糖衣”,本质不增加新功能。
2. 无参装饰器
1 |
|
等价于:
1 | def func(): ... |
3. 有参装饰器
1 |
|
等价于:
1 | def func(): ... |
4. 对比表
| 写法 | 含义 |
|---|---|
@decorator |
直接装饰(无参) |
@decorator(参数) |
先传参,得到装饰器,再装饰(有参) |
5. 其他常见语法糖
1 | # 交换变量 |