Python 语法精炼2
Python 作用域与闭包机制
LEGB 规则
Python 查找变量时遵循四层作用域链:
| 层级 | 名称 | 说明 | 示例 |
|---|---|---|---|
| L | Local | 当前函数内部的变量 | 函数形参或局部定义 |
| E | Enclosing | 外层函数中的变量 | 闭包中的自由变量 |
| G | Global | 当前模块的全局变量 | 脚本或模块顶层定义 |
| B | Built-in | Python 内建命名空间 | len, print, range 等 |
✅ 查找顺序:Local → Enclosing → Global → Built-in
UnboundLocalError 的根源
当 Python 发现函数中有对变量的赋值时,它会在编译阶段将该变量标记为“局部变量”。
1 | def inner(): |
简单来说,Python 在编译函数定义阶段(不是运行时)就会先扫描整个函数体,看哪些变量被赋值(=)。凡是出现过赋值的变量,Python 就认定它是局部变量(Local)。
解决方式:
-
告诉 Python “我想用外层变量”:
nonlocal x→ 使用外层函数作用域变量global x→ 使用全局变量
闭包(Closure)定义
当一个函数嵌套在另一个函数中,且内部函数引用了外层函数的变量,
Python 会在内部函数对象中“捕获”那个外层变量的引用——此时形成闭包。
例:
1 | def outer(): |
✅ 闭包让外层变量在函数退出后依然存在。
常见错误理解
| 误区 | 正解 |
|---|---|
| “每次函数调用都会新建变量” | 仅当外层函数再次执行时才新建作用域 |
“nonlocal 等同于 global” |
nonlocal 仅影响外层函数作用域,不触及全局 |
| “闭包保存的是变量值” | 实际保存的是变量引用 |
闭包陷阱:循环变量与 lambda
经典例子:
1 | funcs = [] |
- 输出:
1 | 2 |
-
原因:
- lambda 捕获的是 i 的引用,循环结束时 i = 2
- 所有 lambda 打印的都是同一个 i 的最终值
解决方法:绑定默认参数
1 | funcs = [] |
列表推导也适用:
1 | funcs = [lambda x=i: x*2 for i in range(4)] |
要点总结
-
LEGB 规则是理解 Python 变量查找的基础。
-
nonlocal 与 global:
nonlocal→ 修改外层函数作用域变量global→ 修改全局变量
-
闭包的核心:
- 捕获的是变量引用而非值
- 外层变量在函数退出后仍然存在
-
循环闭包陷阱:
- 循环变量被 lambda 或函数捕获时,默认捕获引用
- 解决方法:使用默认参数绑定当前值
-
应用场景:
- 闭包可用于状态保持、函数工厂、装饰器实现等
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 Telason!
