跳转至

命名空间与作用域 (Namespaces and Scopes)

在 Python 编程中,理解变量的查找规则至关重要。为什么有时候在一个函数里访问不到外面的变量?为什么有时候会报 UnboundLocalError?这一切都与命名空间作用域有关。

1. 命名空间 (Namespace)

命名空间本质上是一个从名字到对象的映射(就像一个字典)。Python 中主要有三种命名空间:

  1. 内置命名空间 (Built-in):包含 Python 内置的函数(如 print(), len())和异常。程序启动时创建,退出时销毁。
  2. 全局命名空间 (Global):包含模块定义的名称(函数、类、变量)。模块被导入时创建。
  3. 局部命名空间 (Local):包含函数内部定义的名称。函数调用时创建,返回时销毁。

2. 作用域与 LEGB 规则

作用域 (Scope) 是指在程序中可以直接访问某个命名空间的文本区域。

当你在 Python 中访问一个变量时,解释器会按照 LEGB 的顺序查找:

  1. L (Local)局部作用域。当前函数或类的方法内部。
  2. E (Enclosing)嵌套作用域(闭包)。外部嵌套函数的命名空间(从最近的一层往外找)。
  3. G (Global)全局作用域。当前模块的文件级别。
  4. B (Built-in)内置作用域。Python 内置模块。

如果这四层都找不到,就会抛出 NameError

LEGB 示例

x = "Global" # 全局

def outer():
    x = "Enclosing" # 嵌套

    def inner():
        x = "Local" # 局部
        print(f"Inner: {x}")

    inner()
    print(f"Outer: {x}")

outer()
print(f"Global: {x}")

# 输出:
# Inner: Local
# Outer: Enclosing
# Global: Global
注:如果在 inner 中注释掉 x = "Local",它就会打印 "Enclosing"。

3. 修改外部变量:global 关键字

默认情况下,在函数内部对一个变量赋值,会创建一个新的局部变量,而不会修改全局变量。

如果你想在函数内部修改全局变量,必须使用 global 关键字声明。

count = 0

def add():
    # count += 1 # ❌ 报错:UnboundLocalError,因为 Python 认为 count 是局部变量但未初始化
    global count # ✅ 声明我们要使用全局变量 count
    count += 1

add()
print(count) # 1

4. 修改嵌套变量:nonlocal 关键字

如果你在嵌套函数(闭包)中,想要修改外部函数(Enclosing 作用域)的变量,需要使用 nonlocal

def make_counter():
    count = 0

    def counter():
        nonlocal count # 声明我们要引用上一层函数的变量
        count += 1
        return count

    return counter

c = make_counter()
print(c()) # 1
print(c()) # 2
print(c()) # 3

5. 常见陷阱:UnboundLocalError

这是一个非常容易犯的错误。

x = 10

def func():
    print(x) # ❌ 这里会报错!
    x = 20   # 因为这里进行了赋值,Python 编译时认定 x 是局部变量

func()
原因:Python 在编译函数体时,发现 x = 20 赋值语句,因此将 x 标记为局部变量。但是执行到 print(x) 时,局部变量 x 还没有被赋值,所以报错。

解决:要么把 x 声明为 global,要么不要在函数内给 x 赋值(如果只是想读取全局 x)。

总结

  • LEGB 规则决定了变量的查找顺序:局部 -> 嵌套 -> 全局 -> 内置。
  • 在函数内部读取全局变量不需要特殊关键字。
  • 在函数内部修改全局变量需要 global
  • 在嵌套函数内部修改外部函数的变量需要 nonlocal

下一步

解决了“变量在哪里”的问题,我们再来聊聊“变量是什么类型”的问题。虽然 Python 是动态类型的,但在大型项目中,明确的类型信息能显著减少错误。

👉 前往下一章:类型注解 (Type Hints)