文章目录
- [1. Python 变量作用域](#1. Python 变量作用域)
- [2. `global` 关键字](#2.
global
关键字) - [3. `nonlocal` 关键字](#3.
nonlocal
关键字) - [4. 修改 vs 赋值](#4. 修改 vs 赋值)
- [5. 总结](#5. 总结)
本文参考视频链接:
在 Python 中,函数对全局变量的操作存在一个反直觉的特性:仅读取全局变量时正常,若尝试赋值则会报错。我们通过具体案例拆解这一现象。
定义全局变量 count
,函数内仅打印(读取)该变量,程序正常执行:
python
# 定义全局变量
count = 0
def f():
# 读取全局变量 count
print(count)
f() # 输出:0(无报错,正常读取)

若在函数内添加对 count
的赋值操作,即使先读取后赋值,程序也会报错:
python
count = 0
def f():
print(count) # 看似读取全局变量
count = count + 1 # 对 count 赋值
f() # 报错:UnboundLocalError: local variable 'count' referenced before assignment
报错原因:Python 认为函数内的 count
是局部变量 ,但 print(count)
执行时,局部变量 count
尚未赋值,因此抛出"引用未赋值变量"的错误。
1. Python 变量作用域
上述现象的核心是 Python 编译期的变量作用域判断逻辑,而非运行时动态判断。
Python 在编译函数代码时,会扫描函数内部是否存在对某个变量的赋值操作 (变量出现在 =
左侧):
- 若存在赋值操作 :Python 默认该变量是局部变量,函数内所有对该变量的操作都指向局部变量;
- 若不存在赋值操作:Python 会向上查找变量(先局部→再嵌套作用域→再全局→最后内置),此时读取的是全局变量。
关键:判断依据是"是否有赋值操作",与赋值语句的位置顺序无关(即使赋值在读取之后,仍默认变量为局部)。
Python 编译后的字节码能直观体现这一规则:
-
仅读取全局变量 (案例1):函数内使用
load_global
指令,从全局作用域加载变量; -
函数内赋值变量 (案例2):函数内使用
load_fast
指令,从局部作用域加载变量(但局部变量未赋值,故报错)。
简言之:赋值操作让 Python 把变量"标记"为局部,读取时优先找局部作用域,找不到则报错。
2. global
关键字
若需在函数内修改全局变量 ,必须通过 global
关键字显式声明:"该变量是全局变量,而非局部变量"。
在函数开头添加 global count
,明确变量归属,程序可正常修改全局变量:
python
count = 0
def f():
# 显式声明:count 是全局变量
global count
print(count) # 读取全局变量(此时指向全局)
count = count + 1 # 修改全局变量
f() # 输出:0
print(count) # 输出:1(全局变量已被修改)
global
的作用:
- 告诉 Python 编译器:函数内的
count
不是局部变量,而是全局作用域中定义的变量; - 函数内对
count
的赋值操作,会直接修改全局变量,而非创建局部变量。
3. nonlocal
关键字
函数嵌套(闭包)场景中,内层函数对"外层函数的局部变量"的操作,与全局变量问题类似:仅读取正常,赋值则默认内层局部变量。
外层函数 g
定义变量 count
,内层函数 f
尝试赋值该变量,结果要么报错,要么修改无效:
python
def g():
# 外层函数的局部变量(非全局)
count = 0
def f():
# 尝试修改外层函数的 count
count = 1 # 赋值操作→默认是内层函数的局部变量
f()
print(count) # 输出:0(外层 count 未被修改,内层修改的是局部变量)
若先读取后赋值,同样会报错(与全局变量案例2逻辑一致):
python
def g():
count = 0
def f():
print(count) # 内层局部变量 count 未赋值
count = 1
f() # 报错:UnboundLocalError: local variable 'count' referenced before assignment
global
用于声明"全局变量",而 nonlocal
用于声明"嵌套作用域的变量"(即外层函数的局部变量,非全局、非内层局部)。
python
def g():
count = 0
def f():
# 显式声明:count 是嵌套作用域(外层 g 的局部变量)
nonlocal count
print(count) # 读取外层变量
count = 1 # 修改外层变量
f()
print(count) # 输出:1(外层 count 已被修改)
g() # 执行结果:0 → 1(无报错)
nonlocal
只能用于嵌套作用域(内层函数引用外层函数的局部变量),不能用于全局变量;- 若外层作用域中无该变量,
nonlocal
会报错(无法像global
那样"创建"全局变量)。

4. 修改 vs 赋值
前面提到"全局变量可读不可写",但对全局可变对象(如字典、列表、集合),函数内修改其内容时却不会报错。这是因为"修改对象内容"与"变量赋值"是两个不同的操作。
python
# 定义全局可变对象(字典)
d = {"a": 1}
def f():
# 修改字典 d 的内容(不是对 d 变量赋值)
d["a"] = 2
print(d)
f() # 输出:{'a': 2}(无报错,全局字典被修改)
- 变量赋值 :
d = {"b": 3}
→ 改变变量d
的指向(让d
指向新的字典对象),属于"对变量赋值",会触发"赋值即局部"规则; - 对象内容修改 :
d["a"] = 2
→ 未改变变量d
的指向(d
仍指向原全局字典),仅修改了d
指向的对象内部数据,不属于"对变量赋值",因此 Python 仍认为d
是全局变量,使用load_global
指令读取。
若对全局字典 d
直接赋值(改变指向),仍需 global
声明,否则报错:
python
d = {"a": 1}
def f():
# 对全局变量 d 赋值(改变指向)
d = {"b": 3} # 赋值操作→默认 d 是局部变量
f() # 无报错,但全局 d 未被修改
print(d) # 输出:{'a': 1}(全局 d 仍为原对象)
若需修改全局变量 d
的指向,需添加 global d
声明:
python
def f():
global d
d = {"b": 3}
f()
print(d) # 输出:{'b': 3}(全局 d 被修改)
5. 总结
- "赋值即局部" :函数/内层函数中,若变量有赋值操作(
=
左侧),默认是局部变量,与赋值位置无关; global
管全局 :需在函数内修改"全局变量的指向"时,必须用global
显式声明;nonlocal
管嵌套 :需在闭包内层修改"外层函数的局部变量"时,必须用nonlocal
显式声明。
关键字 | 适用场景 | 作用 | 注意事项 |
---|---|---|---|
global |
函数内操作全局变量(需赋值时) | 声明变量是"全局作用域"的,而非局部 | 可修改全局变量的指向(如 d = new_obj ) |
nonlocal |
闭包内层函数操作外层函数的局部变量(需赋值时) | 声明变量是"外层嵌套作用域"的,非局部/非全局 | 不能用于全局变量;外层作用域必须存在该变量 |