Skip to content

Python 命名空间与变量作用域(LEGB)

本篇说明 命名空间作用域,以及解释器如何按 LEGB 顺序解析名字。

什么是命名空间?

命名空间是把名字映射到对象的容器。Python 里「一切皆对象」,我们用名字引用对象,本质上就是在某个命名空间里做查找。

可以把它理解成「以名为键」的映射(概念上类似字典,但实现不一定是 dict):

text
# 示意:同一时刻不同命名空间里可以出现相同的名字,指向不同对象
# 全局:  x -> 10
# 外层函数局部: x -> 20
# 内层函数局部: x -> 30

重要:不同命名空间中可以重复使用同一个名字,它们互不冲突,各自绑定到可能不同的对象。

作用域与命名空间的关系

作用域是源码中的一段文本区域:在该区域内,某个命名空间里的名字可以直接使用(「可见」)。
一个命名空间对应「一块存放名字绑定的地方」;作用域描述的是「在哪儿能用到这些绑定」。

Python 中,类定义函数定义模块等会参与作用域的划分;普通的 for / try 语句块本身并不会再单独造一层函数式局部作用域——在函数里的 for 循环变量,通常仍属于该函数的局部作用域(与循环、分支写在同一函数中时共享同一套局部名字)。
(推导式、部分特殊构造有自己单独的名字规则,进阶时再区分即可。)

命名空间的常见分类与生命周期(概念)

类型含义(概要)生命周期(概要)
局部(Local)当前函数执行时的局部名字函数调用进入时构建,返回后销毁(实现细节可理解为帧)
闭包 / 外层(Enclosed)嵌套函数中,内层可见的「外层函数」的局部名字随外层函数的局部生命周期
全局(Global)当前模块顶层的名字模块被加载后存在,持续到解释器退出(通常)
内置(Built-in)lenint 等内置名所在的名字空间解释器启动后存在

说明:「局部」一般指函数体内,而不是「每一个 fortry 各有一个独立命名空间」。

LEGB 解析规则

在函数体内部引用一个名字时,解释器按下面顺序查找,先找到即用,全部找不到则抛出 NameError

Local(局部)→ Enclosed(闭包外层)→ Global(全局)→ Built-in(内置)

简称 LEGB

shell
>>> name = "global"
>>> def outer():
...     name = "enclosed"
...     def inner():
...         name = "local"
...         print(name)
...     inner()
...
>>> outer()
local

若在 inner 里不写 name = ...,则会向外层继续找,此处略;若需写入外层或全局的名字,要用 nonlocal / global(见下节)。

模块与属性的「命名空间」

import 会加载模块,模块有自己的全局命名空间;用 . 访问的是该对象的属性命名空间(例如模块里的函数、常量)。

shell
>>> import math
>>> math.pi
3.141592653589793

实例属性同理:

shell
>>> obj = object()
>>> obj.__doc__
'The most base type'

找不到名字会报错:

shell
>>> not_defined_anywhere
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'not_defined_anywhere' is not defined

综合示例:多个作用域中的同名 x

python
x = 10

print(f"x is {x}")  # 全局


def outer():
    x = 20
    print(f"x is {x}")  # outer 的局部

    def inner():
        x = 30
        print(f"x is {x}")  # inner 的局部
        print(len("abc"))  # len 来自 Built-in

    inner()


outer()

输出中三处 x 分别对应全局、outer 局部、inner 局部,互不混淆。

globalnonlocal

默认情况下,在函数内赋值的名字会被视为局部,除非声明:

  • global x:函数内的 x模块全局x,赋值会改全局绑定。
  • nonlocal x:用于嵌套函数x最近一层外层函数的局部变量,赋值会改外层函数的绑定。

滥用 global 会降低可读性;能靠参数与返回值传递时,通常更清晰。

小结

  • 命名空间存「名字 → 对象」的映射;作用域决定在代码哪一段能直接访问哪些名字。
  • 函数内找名字用 LEGB;找不到则 NameError
  • 嵌套函数才有典型的 Enclosed内置名在最后兜底。
  • 要在内层函数里修改外层或模块顶层的变量,需 nonlocal / global

参考