文章
合集Python 语言基础第 12 / 21 篇

Python 命名空间与作用域

摘要

namespace (命名空间)是从名称到对象的映射。现在,大多数命名空间都使用 Python 字典实现,但除非涉及到性能优化,我们一般不会关注这方面的事情,而且将来也可能会改变这种方式。

  • 命名空间的例子:
    • 内置名称集合(包括 abs() 函数以及内置异常的名称等)
    • 一个模块的全局名称
    • 一个函数调用中的局部名称
    • ······
  • 命名空间是在不同时刻创建的,且拥有不同的生命周期。

一个命名空间的 作用域 是 Python 代码中的一段文本区域,从这个区域可直接访问该命名空间。

总结:命名空间 = 装变量的 " 盒子 ",作用域 = 你能用到这个盒子的 " 范围 "

查找顺序(从内到外)

def outer():
    x = "我是外层函数的变量"  # ← 第2层:闭包作用域(nonlocal)
    
    def inner():
        x = "我是局部变量"   # ← 第1层:最内层(局部)
        print(x)              # 先找这里
    
    inner()

# 第3层:全局(模块级别)
# 第4层:内置(如 len, print)
规则说明代码示例
LEGB 查找Local → Enclosing → Global → Built-inprint(x) 按这个顺序找
赋值即局部函数内赋值 x = ...,x 就是局部变量见下方陷阱 ⚠️
global/nonlocal声明后才可修改外层变量global x 或 nonlocal x

经典陷阱:UnboundLocalError

x = 10  # 全局

def foo():
    print(x)  # 报错!因为下面有赋值,Python 认定 x 是局部变量
    x = 20    # 赋值让 x 变成局部,但上面已经用了

foo()

解决方法:

def foo():
    global x   # 声明:我要用全局的 x
    print(x)   # ✓ 正常
    x = 20

global 与 nonlocal 的使用

Python 变量声明即赋值,赋值即为局部变量,如果想要在一个作用域内使用外部的变量(外层变量和全局变量),就可以声明为**global/nonlocal**,两者的差别使用一个例子即可观察清楚

def outer():
    x = "outer"
    
    def middle():
        # middle 自己没有 x
        
        def inner():
            nonlocal x      # ← 跳过 middle,直接找到 outer 的 x
            x = "modified"
        
        inner()
        print("middle:", x)  # → "modified"(middle 也能看到变化)
    
    middle()
    print("outer:", x)       # → "modified"

outer()
  • nonlocal 会逐层向外查找**,找到最近的外层变量,但是无法跨到全局
  • global会从全局变量进行捕获,如果全局变量没有,则会创建新的全局变量
声明目标作用未声明时的行为
(默认)局部函数内赋值即创建局部变量赋值即局部,读取会报错(如果先读后写)
global全局强制使用模块级变量创建新的全局变量(如果不存在)
nonlocal闭包强制使用外层函数的变量逐层向外找最近的定义,跳过无此变量的层