想学好 Python ,一定要掌握变量作用域,因为它会像幽灵👻一样,时不时跳出来吓你一跳。你是不是遇到过这种情况:
python
x = 10
def func():
print(x)
func() # 还能跑
def func2():
print(y)
y = 20
func2() # 报错!UnboundLocalError: local variable 'y' referenced before assignment
同样是 print(x)
,x
正常输出,而 y
却报错了?Python 你是不是针对我?!
别慌,今天我们就来彻底搞清楚 Python 的命名空间(Namespace)和作用域(Scope),保证你下次见到类似问题不再抓狂。
1. 什么是命名空间?
命名空间(Namespace) 就是一个存储变量名和对应值的地方,可以理解成一个大字典,里面存着所有变量的名称和它们的值。Python 里的命名空间主要有三类:
- 内置命名空间(Built-in Namespace) :Python 自带的,比如
print()
、len()
这些常用函数。 - 全局命名空间(Global Namespace) :在模块层级(也就是
.py
文件的最外层)定义的变量、函数等。 - 局部命名空间(Local Namespace):函数内部定义的变量、参数等。
简单理解:
- 内置命名空间 是 Python 自带的词典。
- 全局命名空间 是你写的
.py
文件中的变量字典。 - 局部命名空间 是函数内部专属的小字典。
来看个示例:
python
# 全局命名空间
global_var = "我是全局变量"
def my_func():
# 局部命名空间
local_var = "我是局部变量"
print(local_var)
print(global_var) # ✅ 正常打印
my_func() # ✅ 正常打印
print(local_var) # ❌ NameError: name 'local_var' is not defined
函数 my_func()
里面的 local_var
只能在函数内访问,出了函数就不认识了。
2. 什么是作用域?
作用域(Scope)决定了变量能在代码的哪些位置被访问。Python 遵循 LEGB 规则,即:
- Local(局部作用域):当前函数内部的命名空间。
- Enclosing(闭包作用域):嵌套函数(函数里面再定义函数)外层函数的命名空间。
- Global(全局作用域):当前模块的命名空间。
- Built-in(内置作用域):Python 预定义的命名空间。
如果 Python 需要访问一个变量,它会按照 LEGB 顺序查找。来看个案例:
python
x = "全局变量"
def outer():
x = "外部函数变量"
def inner():
x = "内部函数变量"
print(x) # 优先找最近的 x
inner()
print(x) # 找不到 inner 里的 x,找外部函数的
outer()
print(x) # 直接找全局的
先别急着看结果,仔细思考下会输出什么?相信好多人会栽在这里 | | | | | | | | | V
输出结果:
内部函数变量
外部函数变量
全局变量
这个例子很清楚地展示了 LEGB 规则。Python 先找局部变量(Local),找不到就往外层找,一直找到内置命名空间为止。
3. global
和 nonlocal
关键字
有时候我们希望在函数内部修改外部变量,比如:
python
x = 10
def change_global():
global x # 说明我们要修改全局变量 x
x = 20
change_global()
print(x) # 20
如果你不加 global
,x = 20
只会创建一个新的局部变量,不会影响全局 x
。
类似的,nonlocal
用来修改闭包作用域的变量:
python
def outer():
x = "外部变量"
def inner():
nonlocal x # 说明我们要修改外部函数的 x
x = "修改后的变量"
inner()
print(x) # "修改后的变量"
outer()
如果不加 nonlocal
,inner()
里改的 x
只是一个新的局部变量,不会影响 outer()
里的 x
。
4. 常见作用域坑
4.1 for 循环里的变量是全局的!
python
for i in range(5):
pass
print(i) # 4 (在别的语言里可能报错)
Python 里 for
循环变量 i
是全局的,循环结束后仍然存在!
4.2 默认参数的坑
python
def func(x=[]):
x.append(1)
print(x)
func() # [1]
func() # [1, 1](惊不惊喜?)
原因是默认参数 x=[]
只初始化一次,后续调用会复用这个列表!
5. 总结
- Python 的命名空间可以理解成存变量的字典,有 内置、全局、局部 三种。
- Python 遵循 LEGB 规则 查找变量,从局部到全局依次查找。
global
关键字用来修改全局变量,nonlocal
关键字用来修改闭包变量。for
循环变量是全局的,不像其他语言会自动销毁。- 默认参数的可变对象会复用,导致意外行为。
希望这篇文章能让你彻底搞清楚 Python 的作用域和命名空间!下次遇到变量找不到的问题,就从 LEGB 规则入手排查吧!🎉