在 Python 中,
命名空间(Namespace)和
作用域(Scope)是理解变量访问规则的核心概念。它们共同决定了 “变量在哪里定义” 以及 “在哪里可以被访问”,直接影响代码的可读性和正确性。本文将从基础定义出发,结合实例详解 Python3 的命名空间与作用域规则。
命名空间本质是一个映射表(键值对集合),其中 “键” 是变量名,“值” 是变量指向的对象(如数值、函数、类等)。它的核心作用是避免变量名冲突—— 不同命名空间中可以有同名变量,彼此互不干扰。
Python 中命名空间按 “定义位置” 和 “生命周期” 可分为 4 类,从 “最内层” 到 “最外层” 依次为:
Python 提供了两个内置函数用于查看当前命名空间的内容:
locals():返回当前局部命名空间的字典(可修改,修改会影响局部变量)。
globals():返回当前全局命名空间的字典(修改会影响全局变量)。
示例:
输出:
局部命名空间: {'local_var': '我是局部变量'}
全局命名空间: 我是全局变量
作用域是指命名空间的可访问范围—— 即 “在代码的哪个部分可以引用该命名空间中的变量”。它不是物理容器,而是一套 “访问规则”,决定了变量查找的顺序。
Python 中变量的查找遵循LEGB 规则(从内到外依次查找,找到即停止):
- L(Local):当前函数 / 方法的局部作用域(对应局部命名空间)。
- E(Enclosing):嵌套函数的外层函数作用域(对应非局部命名空间)。
- G(Global):当前模块的全局作用域(对应全局命名空间)。
- B(Built-in):Python 内置作用域(对应内置命名空间)。
若在所有作用域中都未找到变量,则抛出NameError。
- 若
x在inner()内定义(c=30改为x=30),则 L 作用域找到,输出30。
- 若
x未在inner()内定义,但在outer()内定义(b=20改为x=20),则 E 作用域找到,输出20。
- 若
x未在outer()内定义,但在模块级定义(a=10改为x=10),则 G 作用域找到,输出10。
- 若
x未在模块级定义,但x是内置函数(如x=len),则 B 作用域找到,输出<built-in function len>。
- 若以上都没有,则抛出
NameError: name 'x' is not defined。
当局部变量与全局变量同名时,局部变量会 “遮蔽” 全局变量(即优先访问局部变量):
默认情况下,在局部作用域中不能直接修改外层作用域的变量(只能读取)。若需修改,需用关键字声明:
global:声明变量为 “全局变量”(修改全局命名空间中的变量)。
nonlocal:声明变量为 “非局部变量”(修改嵌套外层函数的变量,不适用于全局变量)。
global示例:
nonlocal示例(嵌套函数):
def outer():
outer_var = 100
注意:
nonlocal不能用于修改全局变量(会报错)。
- 若未声明
global/nonlocal,在局部作用域中对变量赋值时,Python 会默认将其视为 “新的局部变量”(而非修改外层变量)。
- 模块:每个
.py文件都是一个模块,拥有独立的全局命名空间。不同模块的全局变量互不干扰(如module1.var和module2.var可同名)。
- 类:类定义会创建独立的命名空间(类属性、方法存储其中);类的实例也有自己的命名空间(实例属性)。访问规则为:先查实例命名空间,再查类命名空间。
class MyClass:
class_var = "类属性(类命名空间)"
- 误区:“局部作用域可以修改外层变量”—— 实际默认只能读取,修改需用
global/nonlocal。
- 误区:“
locals()和globals()返回的是副本”—— 实际返回的是命名空间的引用,修改字典会直接影响变量。
- 核心规则:变量查找遵循 LEGB 顺序,作用域决定 “能否访问”,命名空间决定 “变量存储位置”。
通过理解命名空间的 “容器” 角色和作用域的 “访问规则”,可以更清晰地掌控 Python 变量的行为,避免因变量名冲突或访问权限导致的错误。记住 LEGB 规则和global/nonlocal的用法,是掌握 Python 变量作用域的关键。