摘要:在 Python 中,我们常常听到作用域、全局变量、局部变量这些概念。它们背后都有一个统一的核心机制:命名空间(Namespace)。
在 Python 中,我们常常听到作用域、全局变量、局部变量这些概念。它们背后都有一个统一的核心机制:命名空间(Namespace)。
理解命名空间及其生命周期,有助于我们掌握变量查找规则(LEGB 原则),写出更清晰、健壮的代码。
一、命名空间与作用域
1、命名空间
命名空间(Namespace)描述的是名字(name)与对象(object)之间的绑定关系,本质是一个字典。
名字:可以是变量名、函数名、类名。
对象:这些名字所引用的值。
比如:
x = 42print(type(globals)) #print("x" in globals) # Trueprint(globals["x"] == 42) # True当我们在模块顶层写下 x = 42 时,解释器会将名字 'x' 绑定到对象 42,并存入当前模块的全局命名空间字典。这个字典可以通过调用 globals 获取。
2、作用域
作用域(Scope)描述的是“名字在代码中可见和可访问的范围”。
命名空间是存储,作用域是访问规则。
在 Python 中,作用域链正是由不同层级的命名空间构成。
二、命名空间的分类
Python 程序运行时,会动态创建和销毁命名空间。主要有以下四类:
1、内建命名空间
Built-in Namespace
内建命名空间包含 Python 启动时自动加载的内建函数和异常,如 len、abs、Exception 等。
import builtinsprint(len("hello")) # 内建函数print("len" in dir(builtins)) # True内建命名空间存储在 模块中。
生命周期:
解释器启动时创建,解释器退出时销毁。
作用域:
在任何位置都可以直接访问(除非被屏蔽)。
2、全局命名空间
Global Namespace
在模块层级(不在任何函数或类中)定义的变量、函数、类等。
x = 10 # 全局变量def foo:print(x)foo # 可以访问全局命名空间存储在模块对象的特殊属性(module.__dict__)中。
生命周期:
模块加载时创建,通常伴随程序结束才销毁。模块卸载时可能提前销毁。
作用域:
在模块内部任何地方都可访问(除非被屏蔽)。
在函数内部要修改则需用 global 关键字声明。
3、局部命名空间
Local Namespace
局部命名空间主要包括两种情况:函数调用时的局部环境 和类定义体执行时的临时环境。
(1)函数调用的局部命名空间
当函数调用时,Python 会新建一个局部命名空间,用于存放函数的参数和局部变量。
def bar(y):z = y + 1 # z 在局部命名空间print(locals)bar(5) # {'y': 5, 'z': 6}在 CPython 中,这些局部变量并不真的保存在字典里,而是编译期分配到 fast locals 数组(槽位),运行时通过索引访问,以提升效率。
调用 或访问函数的 f_locals 时,才会把这些变量复制成字典返回。
生命周期:
函数调用时创建,函数返回后销毁。若被闭包引用,则生命周期延长。
作用域:
仅在函数体内可访问,外部无法直接使用。
(2)类定义体的局部命名空间
在执行类定义时,Python 也会创建一个临时的局部命名空间字典,用于存放类体中定义的属性和方法。类定义完成后,这个字典会成为类对象的 __dict__ 属性。
class Demo:a = 1def method(self):return "hi"print(Demo.__dict__["a"]) # 1print("method" in Demo.__dict__) # True类的命名空间属于局部命名空间的一种,但它不参与 LEGB 查找。在方法中访问变量时,解释器不会去类的 __dict__ 查找,而是依赖实例属性或作用域链。
生命周期:
类定义执行时创建,执行完成后转为类对象的属性字典,并随类对象一同存活。
作用域:
类体内的变量仅在类定义执行期间可见,类体执行完成后,这个字典就成为类对象的属性字典。
4、嵌套命名空间
Enclosing Namespace
外层函数的局部变量,一旦被内部函数引用,就会被“提升”为自由变量(free variable),存储到 cell 对象中。
def outer:msg = "hello"def inner:print(msg) # 访问外层作用域innerouter这些 cell 对象会挂在内部函数对象的 __closure__ 属性上。
生命周期:
外层函数执行期间存在,若闭包引用则延长其生命周期。
作用域:
内部函数执行时,解释器会根据代码对象 (co_freevars) 找到对应的 cell 并访问。
三、变量查找规则:LEGB 原则
当 Python 遇到一个名字时,会按以下先后顺序解析,简称“LEGB 原则”。
L(Local) —— 当前函数的局部命名空间
E(Enclosing) —— 外层函数的命名空间
G(Global) —— 当前模块的全局命名空间
B(Built-in) —— Python 内建命名空间
示例 1:
x = "global"def outer:x = "enclosing"def inner:x = "local" # x 为新的局部变量print(x)innerouter # 输出 localprint(x) 会从局部命名空间中开始查找 x,若未找到,会依次向外层查找,直至全局或内建命名空间。
若所有层级都未找到,则抛出 NameError。
示例 2:
def outer:msg = "hello" # Enclosing:外层函数的局部变量def inner:print(msg) # 引用了外层作用域return innerf = outer # outer 返回后按理 msg 应该销毁f # hello —— 但闭包让 msg 存活在 cell 对象中四、常见错误示例
1、变量名称遮蔽内建名称
list = [1, 2, 3]print(list) # [1, 2, 3]print(list("123")) # ❌ 报错出错的原因是变量名称 list 覆盖了内建类型 list。
因此,应避免使用 list、sum、str、input 等内建名称作为变量名。
2、未声明的全局变量引用
x = 10def test:print(x) # ❌ 报错:UnboundLocalErrorx = 20test因为 x = 20 出现在函数体内,Python 将 x 判定为局部变量,但在赋值前访问导致报错。
解决方法:用 global 声明全局变量。
x = 10def test:global xprint(x) # 10x = 20 # 修改全局变量 x 的值testprint(x) # 203、修改外层函数变量的值
内层函数不能直接赋值修改外层函数的变量,否则会被当作新建局部变量。
如需修改,需使用 nonlocal 关键字。
def outer:x = 5def inner:nonlocal xx += 1print(x)innerouter # 6nonlocal 声明让 inner 修改外层函数的变量,而不是创建新的局部变量。
五、使用建议
1、避免命名冲突
使用有意义的变量名,避免覆盖内建函数名(如不要用 len = 10)。
2、减少全局变量
全局变量容易引发混乱,推荐封装到函数或类中。
3、利用命名空间调试
Python 提供了三个内置函数来操作命名空间:
:返回当前模块的全局命名空间(字典)。
:返回当前局部命名空间(字典)。
:返回对象的属性命名空间(通常是 __dict__)。
a = 1print(globals.keys) # 查看全局变量def test:b = 2print(locals) # 查看局部变量test小结
命名空间 = 名字 → 对象的映射表。
Python 有四类命名空间:内建、全局、局部、嵌套。
类命名空间是局部命名空间的一种特殊形式,不参与 LEGB 名字查找。
变量查找遵循 LEGB 原则。
常见错误有 UnboundLocalError,需要关键字 global 或 nonlocal 来解决。
调试时可用 globals、locals、vars 等工具辅助观察。
“点赞有美意,赞赏是鼓励”
来源:安博新高考