在 Python 中,变量名在什么地方定义、在什么地方被访问,直接决定了程序的行为。理解 Python 的变量查找规则,是写出正确、可维护函数代码的关键。
作用域与 LEGB 规则
Python 在查找一个变量名时,会按照固定的顺序搜索四层作用域,合称 LEGB:
- L (Local):局部作用域。当前函数内部定义的变量,每次函数调用时创建。
- E (Enclosing):闭包作用域。外层函数(如果存在嵌套)的局部作用域,即“包裹”当前函数的外部函数。
- G (Global):全局作用域。模块顶层定义的变量,或者在函数内用
global声明的变量。 - B (Built-in):内置作用域。Python 内置的名字空间,如
print、len、int等。
查找顺序 就是 L → E → G → B,一旦找到就不会继续向外搜索。如果四层都没有,则抛出 NameError。
一个例子看清 LEGB 规则:
x = "global" # G (全局作用域)
def outer():
x = "enclosing" # E (闭包作用域,相对于 inner 而言)
def inner():
x = "local" # L (局部作用域)
print(x) # 输出 local
inner()
outer()
print(x) # 输出 global
在 inner 中访问 x 时,先在局部作用域 L 中找到 "local",就停在这里。若注释掉 inner 里定义 x 的那一行,则会向外查找:E 层 outer 中的 "enclosing",再外层才是全局 G 层的 "global"。
global 关键字:在函数内修改全局变量
在函数内部,如果仅仅读取全局变量,不需要额外声明。但是如果想在函数内给全局变量重新赋值(即改变其绑定),就必须通过 global 关键字将其声明为来自全局作用域,否则 Python 会默认创建一个同名局部变量。
count = 0
def increment():
global count
count = count + 1 # 如果没有 global,这行会报 UnboundLocalError
常见错误:在函数内对一个全局变量进行 += 或 = 赋值操作,Python 会将 count 视为局部变量,而局部变量在使用前未赋值,导致 UnboundLocalError。使用 global 明确声明即可解决。
注意:如果全局变量是可变对象(如列表、字典),只修改其内部元素(例如 global_list.append(1)),不需要 global 声明,因为对象引用的绑定没有变。但若要对列表整体重新赋值(global_list = [1, 2]),就需要 global。
nonlocal 关键字:在嵌套函数中修改外层非全局变量
nonlocal 专门用于嵌套函数中,用来操作位于外层(E)作用域的变量,而非全局变量。声明了 nonlocal,就可以在内部函数中重新绑定外层(非全局)的变量。
def outer():
n = 10
def inner():
nonlocal n
n = 20 # 修改的是 outer 的 n
inner()
print(n) # 输出 20
outer()
如果没有 nonlocal,inner 中的 n = 20 会创建一个属于 inner 自己的局部变量,外层的 n 不会被修改。
使用场景:常见于闭包或装饰器需要保存状态的时候。例如实现一个计数器:
def counter(start=0):
count = start
def increment():
nonlocal count
count += 1
return count
return increment
c = counter(5)
print(c()) # 6
print(c()) # 7
global 与 nonlocal 的区别总结
| 关键字 | 作用域指向 | 适用场景 |
|------------|------------------|----------------------------|
| global | 全局作用域 G | 在函数中修改模块级变量 |
| nonlocal | 外层闭包作用域 E | 在嵌套函数中修改外层函数变量 |
实践中的避坑建议
- 尽量少用
global:全局变量让代码难以测试和维护。优先考虑函数参数和返回值来传递数据。 - 用
nonlocal实现简单的状态保持,但若逻辑变复杂,应考虑类或对象来封装状态。 - 理解可变对象与重新赋值的区别:
list.append()不需要global,但list = something则需要。这一点常常导致困惑。 - 利用 LEG 中的隐式屏蔽:外层变量在内层被赋值时会被局部变量屏蔽,这是很多奇怪错误的来源。使用 IDE 的静态检查或
flake8可以提前发现潜在的作用域问题。
掌握了 LEGB 规则和这两个关键字,就能准确控制 Python 函数中变量可见性和生命周期,避免难以排查的作用域错误。