Python函数中局部变量与全局变量的区别是什么

南乔
南乔
发布时间:2026-11-01| 8人浏览过
原创

深入解析Python函数中局部变量与全局变量的核心区别,涵盖作用域、生命周期、LEGB查找机制、UnboundLocalError报错根源与变量修改规范。

一、变量定义位置与生命周期边界

理解两者的第一步是观察它们在代码中“出生”的位置以及何时被内存释放。Python依据变量初次绑定的代码块位置,将其天然划分为局部变量与全局变量。

局部变量(Local Variable)是在函数内部通过赋值或形参声明的变量。它的生命周期从函数被调用开始,到函数执行完成、返回结果时终止。当函数退出时,局部命名空间随对应的栈帧被回收,局部变量所占用的内存引用通常也会立即销毁。因此,在函数外部无法直接读取或引用该变量。

全局变量(Global Variable)则定义在模块(即 .py 脚本)的顶层,没有任何代码缩进。它的生命周期从当前模块加载执行开始,直到整个Python解释器进程结束。在同一个模块内,无论是普通代码行还是后续定义的任意函数,默认都可以看到这个全局变量。

基础定义示例
# 全局变量:定义在函数外层
app_version = "2.1.0"

def show_info():
    # 局部变量:仅在函数运行期存在
    user_name = "Alice"
    print(f"用户: {user_name}, 版本: {app_version}")

show_info()
# 正常输出:用户: Alice, 版本: 2.1.0

# 外部无法访问函数内的局部变量
# print(user_name) 
# 会直接抛出异常: NameError: name 'user_name' is not defined
Python函数中局部变量与全局变量的区别是什么
图 1:局部变量与全局变量的作用域边界及内存生命周期演进示意

二、Python名称解析机制:LEGB原则

当你在函数中直接书写一个变量名称时,Python是如何决定去读取局部变量还是全局变量的?这依赖于固定的LEGB规则。解释器会严格按照以下顺序从内向外逐层寻找匹配的变量名:

  1. L (Local):最先在当前函数内部的局部作用域中搜索。
  2. E (Enclosing):如果当前函数是嵌套在其他函数内部的闭包,则向上一级外层函数的局部作用域搜索。
  3. G (Global):如果内层均未找到,前往当前模块文件的顶层全局作用域搜索。
  4. B (Built-in):最后在Python内置命名空间中搜索(如 len、range、print 等)。

如果在以上四层空间中均未查找到对应名称,解释器将立即抛出 NameError。这个单向从内向外的查找逻辑决定了一个重要特性:变量遮蔽(Shadowing)。如果函数内部声明了一个与全局变量同名的局部变量,函数内的操作将优先使用局部变量,外部全局变量被“遮蔽”,但全局变量的值本身完全不受影响。

变量遮蔽示例
val = 100  # 全局变量

def demo():
    val = 20  # 局部变量,发生遮蔽,优先命中 Local 级
    print("函数内 val:", val)

demo()
print("全局 val:", val)

# 示例输出:
# 函数内 val: 20
# 全局 val: 100
Python函数中局部变量与全局变量的区别是什么
图 2:Python 变量名称自内向外(LEGB)解析机制与查找顺序流

三、经典踩坑:UnboundLocalError的成因与修复

初学者在编写函数时经常会遇到一个困惑的报错:明明已经在全局定义了变量 counter = 10,为什么在函数内部执行 print(counter) 随后写了一句 counter = counter + 1 时,第一行输出就会直接崩溃?

经典报错复现代码
counter = 10

def increment():
    print(counter)      # 这里直接抛出异常!
    counter = counter + 1

increment()

# 报错信息示例:
# UnboundLocalError: local variable 'counter' referenced before assignment

为什么会发生这种情况?
Python在运行函数之前,会先对函数的代码进行静态编译。当编译器在 increment 函数体内扫描到赋值语句 counter = ... 时,编译器会果断地将 counter 标记为当前函数的局部变量。这一决策是在静态编译期确定的,而不是一行行动态解释时才决定的。

因此,当函数执行到第一行 print(counter) 时,解释器已经将 counter 锁定在 Local 局部命名空间中,不再向外层 Global 查找。但此时该局部变量尚未完成赋值绑定,因而直接抛出 UnboundLocalError(局部变量在赋值前被引用)。

解决路径一:使用 global 关键字显式声明

如果你确实需要直接在函数内部修改全局变量的值,必须在函数首行使用 global 关键字告诉解释器:“不要把这个变量当作局部变量,所有读写均直接作用于模块顶层的全局变量”。

counter = 10

def increment():
    global counter     # 声明 counter 属于全局命名空间
    print("修改前:", counter)
    counter = counter + 1

increment()
print("全局 counter 现已变为:", counter)

# 示例输出:
# 修改前: 10
# 全局 counter 现已变为: 11

解决路径二:改用参数传递与返回值(推荐工程规范)

虽然 global 能够修复报错,但在实际工程中,过多依赖全局变量会导致函数之间产生隐式耦合,给代码的调试与并发维护带来巨大隐患。更优雅、安全的方式是将数据作为参数传入函数,计算完毕后通过 return 返回新结果:

counter = 10

def calculate_next(current_val):
    return current_val + 1  # 纯函数,不污染外部状态

counter = calculate_next(counter)
print("计算后得到:", counter)
Python函数中局部变量与全局变量的区别是什么
图 3:UnboundLocalError 产生根源及两种应对策略路线图
注意可变对象(如列表、字典)的原地修改:
如果全局变量是可变对象(例如 scores = [80, 90]),在函数内调用 scores.append(100) 并不会触发 UnboundLocalError,也不需要 global 声明。因为此处并未对 scores 本身重新绑定赋值,而是读取该变量后调用了对象自身的方法。但如果是 scores = scores + [100],则属于重新赋值,必须按上述规则处理。

四、核心差异全景对比与工程实践建议

为了在实际开发中准确选用变量并规避作用域混淆,我们将局部变量与全局变量的核心技术特性归纳如下:

Python函数中局部变量与全局变量的区别是什么
图 4:Python局部变量与全局变量全景特征对比与工程设计矩阵
总结要点: 全局变量适合作为模块级的全局常量或共享配置,其只读访问非常简便;而涉及具体逻辑运算、状态迭代和临时计算时,应当坚决使用局部变量配合入参和返回值,以此构建结构清晰、易测试、低耦合的高质量Python代码。

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。