2025-10-07 Python不基础 20——全局变量与自由变量

文章目录

  • [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. 总结

  1. "赋值即局部" :函数/内层函数中,若变量有赋值操作(= 左侧),默认是局部变量,与赋值位置无关;
  2. global 管全局 :需在函数内修改"全局变量的指向"时,必须用 global 显式声明;
  3. nonlocal 管嵌套 :需在闭包内层修改"外层函数的局部变量"时,必须用 nonlocal 显式声明。
关键字 适用场景 作用 注意事项
global 函数内操作全局变量(需赋值时) 声明变量是"全局作用域"的,而非局部 可修改全局变量的指向(如 d = new_obj
nonlocal 闭包内层函数操作外层函数的局部变量(需赋值时) 声明变量是"外层嵌套作用域"的,非局部/非全局 不能用于全局变量;外层作用域必须存在该变量
相关推荐
charlie11451419118 分钟前
现代 Python 学习笔记:Statements & Syntax
笔记·python·学习·教程·基础·现代python·python3.13
Never_Satisfied1 小时前
在JavaScript / Node.js / 抖音小游戏中,使用tt.request通信
开发语言·javascript·node.js
爱吃小胖橘1 小时前
Unity资源加载模块全解析
开发语言·unity·c#·游戏引擎
千里镜宵烛2 小时前
Lua-迭代器
开发语言·junit·lua
渡我白衣3 小时前
C++ 同名全局变量:当符号在链接器中“相遇”
开发语言·c++·人工智能·深度学习·microsoft·语言模型·人机交互
淮北4943 小时前
html + css +js
开发语言·前端·javascript·css·html
麦麦大数据3 小时前
F036 vue+flask中医热性药知识图谱可视化系统vue+flask+echarts+mysql
vue.js·python·mysql·flask·可视化·中医中药
移远通信3 小时前
MQTT协议:物联网时代的通信革命
python·物联网·网络协议
Amo Xiang3 小时前
JavaScript逆向与爬虫实战——基础篇(css反爬之动态字体实现原理及绕过)
爬虫·python·js逆向·动态字体