Python 变量作用域:变量在哪儿能用,Python 按这个顺序找
写 Python 的时候,有时候会遇到这种奇怪的情况:
明明在外面定义了一个变量,函数里一用就报错 UnboundLocalError;或者在函数里改了一个值,外面却纹丝不动。
这些问题都跟变量作用域有关——变量在哪儿能用,Python 是按照一套固定规则去查找的。
变量能找到,全靠 LEGB
Python 找变量的时候,按这个顺序找:局部 → 嵌套 → 全局 → 内置。
记住四个字母:L → E → G → B。
| 缩写 | 全称 | 啥意思 | 举个例子 |
|---|---|---|---|
| L | Local | 当前函数里定义的变量 | 函数内部的 x = 1 |
| E | Enclosing | 外层函数的变量(嵌套函数场景) | 外函数里的 x,内函数能用 |
| G | Global | 模块顶层的变量 | .py 文件最外面定义的变量 |
| B | Built-in | Python 自带的函数和变量 | print、len、range |
从里往外找,找到了就用,找不到就报 NameError。
局部变量(Local):函数里面的,外面用不了
函数里面定义的变量,默认就是局部变量。只在函数执行期间存在,执行完就没了。
1 | def my_func(): |
函数的参数也是局部变量:
1 | def greet(name): # name 是参数,也是局部变量 |
局部变量名可以和全局变量同名,但它们是两个不同的东西:
1 | x = 100 # 全局 |
函数里优先用局部变量,找不到才往外找。
全局变量(Global):外面定义的,函数里能读,但改的时候要注意
访问全局变量,直接读就行:
1 | message = "Hello" |
想修改全局变量?得先声明:
1 | count = 0 |
为什么报错?因为 count += 1 会赋值,Python 看到赋值就认为 count 是局部变量,但局部变量还没初始化,所以报错。
正确写法是用 global 声明:
1 | count = 0 |
global 的规则:必须在修改之前声明,通常放在函数的第一行。
嵌套作用域(Enclosing):函数套函数,内层能用外层的
1 | def outer(): |
内层函数能读外层函数的变量,这是 LEGB 里的 E(Enclosing) 层级。
内层函数想修改外层变量?用 nonlocal:
1 | def outer(): |
nonlocal 和 global 的区别:
global:改的是模块顶层的全局变量nonlocal:改的是外层函数里的变量(在嵌套函数里用)
闭包——内层函数被返回后,外层变量还被”记住”:
1 | def make_multiplier(n): |
这个特性叫闭包,外层函数的变量 n 被内层函数”捕获”了,即使外层函数执行完了,n 依然活着。
内置作用域(Built-in):Python 自带的那些东西
print、len、type、range 这些,都是 Python 内置的,属于 B(Built-in) 层级。
但不小心用了同名变量,内置函数就被”遮蔽”了:
1 | len = 100 # 变量名跟内置函数同名 |
这个问题还挺隐蔽的。你定义了一个叫 len 的变量,就把内置的 len 函数覆盖了。list、dict、str、type 这些名字也容易被覆盖。
最佳实践:别用 Python 内置函数的名字当变量名。
LEGB 查找顺序,看一个完整的例子
1 | # G(全局) |
输出:
1 | inner |
inner 里面的 print(x),从 L 找到就停了,所以用的是 x = "inner"。