作用域与 LEGB 陷阱
函数内修改全局变量为什么报错?UnboundLocalError 详解
- 理解 LEGB 作用域查找规则
- 掌握 global 和 nonlocal 的使用
- 理解 UnboundLocalError 的原因
- 避免作用域相关的常见 bug
作用域与 LEGB 陷阱
理解作用域是从新手进阶到中级开发者的关键一步。
LEGB 规则
Python 查找变量的顺序:Local(函数内)→ Enclosing(外层函数)→ Global(模块级)→ Built-in(内置)。
UnboundLocalError 陷阱
Python 编译函数时就定了哪些是局部变量:只要函数体内任何地方对变量赋值(=、+=、for 循环变量这类都算),这变量就被当成局部变量。哪怕赋值写在引用的后面。这和 JavaScript 的 var 提升像,但更严。
nonlocal:闭包中的修改
可变对象不需要 global
内置函数被覆盖
LEGB 的 B(Built-in)是最后防线如果你用内置函数名做变量名,会覆盖它 list = [1, 2, 3] # 危险!覆盖了内置 list()print(list(range(5))) # TypeError: list is not callable 常见被误覆盖的内置名:list, dict, set, str, int, float, booltype, id, len, sum, min, max, sorted, map, filter, input, open 恢复方法:del list # 删除你的变量,恢复内置 最佳实践:永远不要用内置函数名做变量名!用 names / items / data / users / total 等有意义的名字
全局变量是万恶之源——它让函数有了隐藏的状态依赖,难以测试和调试。真实项目中尽量避免 global。需要跨函数共享状态时,用类(self.count)、闭包(nonlocal)或数据结构传参。linter(如 ruff)会警告重写内置名。
函数内修改全局变量需要加global声明。
资深工程师加餐
底层原理 · 大厂视角 · 工程经验,点卡片展开
list 底层是一段连续的指针数组,容量不够时整体扩容(CPython 约按 1.125 倍)。因此按下标取元素是 O(1),末尾 append 均摊 O(1),但在头部 insert(0,…) 或 pop(0) 要搬动全部元素、是 O(n)。dict/set 基于哈希表,平均查找 O(1) 但更耗内存。选对数据结构,往往比抠语法更影响性能。
挑战任务
安全的计数器
写个计数器,用闭包和 nonlocal 实现,别用全局变量。
课后作业
避免全局变量
用类实现一个计数器,替代全局变量方案。