20 分钟
Python 进阶

作用域与 LEGB 陷阱

函数内修改全局变量为什么报错?UnboundLocalError 详解

  • 理解 LEGB 作用域查找规则
  • 掌握 global 和 nonlocal 的使用
  • 理解 UnboundLocalError 的原因
  • 避免作用域相关的常见 bug

作用域与 LEGB 陷阱

理解作用域是从新手进阶到中级开发者的关键一步。

LEGB 规则

Python 查找变量的顺序:Local(函数内)→ Enclosing(外层函数)→ Global(模块级)→ Built-in(内置)。

示例代码(可运行)

UnboundLocalError 陷阱

示例代码(可运行)
🚫Python 的作用域判定

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)会警告重写内置名。

找 Bug函数里的 total 是局部变量,因为函数里给它赋值了,和外面的全局 total 没关系。但这么写容易搞混。换个变量名,比如 result,就不会歧义了。
total = 100 def add_tax(price): tax_rate = 0.08 total = price * (1 + tax_rate) return total print(add_tax(50)) print(total)
选择题

函数内修改全局变量需要加global声明。

资深工程师加餐

底层原理 · 大厂视角 · 工程经验,点卡片展开

list 底层是一段连续的指针数组,容量不够时整体扩容(CPython 约按 1.125 倍)。因此按下标取元素是 O(1),末尾 append 均摊 O(1),但在头部 insert(0,…) 或 pop(0) 要搬动全部元素、是 O(n)。dict/set 基于哈希表,平均查找 O(1) 但更耗内存。选对数据结构,往往比抠语法更影响性能。

挑战任务

安全的计数器

简单+50 XP

写个计数器,用闭包和 nonlocal 实现,别用全局变量。

安全的计数器
2 个测试用例

课后作业

避免全局变量

中等+20 XP

用类实现一个计数器,替代全局变量方案。

避免全局变量
1 个测试用例