飞翔飞翔
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
  • 学习路径
  • 第1章 Python简介

    • Python是什么
    • 安装与运行
    • 交互式解释器
    • 注释与编码规范
  • 第2章 变量与数据类型

    • 变量与对象
    • 整数 int
    • 浮点数 float
    • 复数 complex
    • 布尔值 bool
    • 字符串 str
    • 空值 None
    • 类型转换
  • 第3章 运算符与表达式

    • 算术运算符
    • 比较运算符
    • 赋值运算符
    • 逻辑运算符
    • 位运算符
    • 身份与成员运算符
    • 海象运算符
    • 运算符优先级
  • 第4章 流程控制

    • if 语句
    • if-else 语句
    • if-elif-else 语句
    • match-case 语句
    • 条件表达式(三元运算符)
    • while 循环
    • for 循环
    • range 函数
    • break 与 continue
    • 循环的 else 子句
    • pass 语句
  • 第5章 数据结构

    • 列表创建与索引
    • 列表方法
    • 列表推导式
    • 元组
    • 序列解包
    • 集合
    • 字典创建与访问
    • 字典方法
    • 字典推导式
    • range 对象
  • 第6章 函数

    • 定义函数
    • 位置参数与关键字参数
    • 默认参数
    • 可变参数
    • 解包实参
    • 函数返回值
    • lambda 表达式
    • 文档字符串与注解
    • 作用域与命名空间
    • global 与 nonlocal
  • 第7章 模块与包

    • 模块导入
    • 模块搜索路径
    • 包与相对导入
    • 标准库概览
  • 第8章 文件与输入输出

    • 文件读写
    • 上下文管理器
    • 字符串格式化
    • JSON 与 CSV
  • 第9章 面向对象

    • 类与对象
    • 方法
    • 实例变量与类变量
    • 私有变量
    • 继承
    • 多重继承
    • 魔术方法
    • 属性装饰器
    • 数据类 dataclass
  • 第10章 异常处理

    • 语法错误与异常
    • try-except
    • 异常链与 raise
    • 清理操作
    • 自定义异常
  • 第11章 迭代器与生成器

    • 迭代器协议
    • 生成器
    • 生成器表达式
    • 迭代工具
  • 第12章 高级特性

    • 装饰器
    • 函数式编程
  • 第13章 工程实践

    • 测试与调试
    • 代码质量
    • 虚拟环境

作用域与命名空间

Python 中变量的可见性和生命周期由命名空间(namespace)和作用域(scope)控制。命名空间是名称到对象的映射,作用域是 Python 代码可以直接访问命名空间的文本区域。理解 LEGB 规则和闭包机制,是掌握 Python 函数高级用法的基础。

命名空间的种类

Python 程序执行期间存在多种命名空间:

  • 局部命名空间:函数调用时创建,包含函数的局部变量。函数返回或抛出异常时销毁。
  • 全局命名空间:模块级别,包含模块的全局变量、函数定义、类定义等。在模块文件执行时创建,持续到解释器退出。
  • 内置命名空间:包含内置函数(如 len、print)和内置异常。Python 启动时创建,永不销毁。

这些命名空间彼此独立,相同名称在不同命名空间中互不干扰:

len = 10          # 全局命名空间中创建 len,遮蔽内置的 len

def demo():
    len = "局部"   # 局部命名空间中创建 len,遮蔽全局的 len
    print(len)     # 局部

demo()
print(len)        # 10(全局)
print(__builtins__.len("abc"))   # 3,通过 __builtins__ 仍可访问内置 len

LEGB 规则

Python 按 LEGB 顺序解析名称:

  1. Local:当前函数的局部命名空间
  2. Enclosing:外层(嵌套)函数的局部命名空间(从内到外逐层查找)
  3. Global:当前模块的全局命名空间
  4. Built-in:内置命名空间
x = "global"

def outer():
    x = "enclosing"
    
    def inner():
        x = "local"
        print(x)      # local —— 先查局部
    
    inner()
    print(x)          # enclosing —— outer 的局部

outer()
print(x)              # global

如果某一层找不到名称,就继续向上一层查找,直到 Built-in。如果 Built-in 也找不到,抛出 NameError。

赋值与作用域的关系

赋值操作决定了一个名称属于哪个作用域。如果在函数内部对变量赋值,Python 默认认为该变量是局部变量,即使外层有同名变量:

x = "global"

def demo():
    print(x)   # 这里会报错!
    x = "local"

demo()
# UnboundLocalError: cannot access local variable 'x' where it is not associated with a value

为什么会报错?因为 Python 在编译函数体时,看到 x = "local" 就把 x 标记为局部变量。执行到 print(x) 时,解释器在局部命名空间中查找 x,但此时还未赋值,所以抛出 UnboundLocalError。

如果确实需要修改全局变量,必须使用 global 声明;如果需要修改外层函数变量,必须使用 nonlocal 声明。这两个关键字详见《global 与 nonlocal》文档。

闭包

闭包(closure)是指嵌套函数引用了外层函数的变量,并且外层函数已经返回,但内层函数仍然可以访问这些变量的机制。闭包让函数"记住"它被创建时的环境:

def make_power(exponent):
    def power(base):
        return base ** exponent    # 引用外层变量 exponent
    return power

square = make_power(2)
cube = make_power(3)

print(square(5))   # 25
print(cube(5))     # 125

这里 square 和 cube 都是闭包。它们各自记住了创建时的 exponent 值(2 和 3),即使 make_power 已经执行完毕。

闭包的关键在于自由变量(free variable):被内层函数引用、但不在内层函数局部命名空间中定义的名称。Python 把自由变量保存在函数对象的 __closure__ 属性中:

print(square.__closure__)           # (<cell at ...: int object at ...>,)
print(square.__closure__[0].cell_contents)   # 2

闭包与可变对象

闭包捕获的是变量引用,不是值的副本。如果自由变量是可变对象,闭包可以观察到外部对它的修改:

def make_accumulator():
    total = [0]          # 用列表包装,使其可变
    
    def add(value):
        total[0] += value   # 修改列表内容,不是重新绑定
        return total[0]
    
    return add

acc = make_accumulator()
print(acc(10))   # 10
print(acc(5))    # 15
print(acc(3))    # 18

注意这里不能写 total += value(等价于 total = total + value,会重新绑定变量),因为闭包中的自由变量默认是只读的,重新绑定需要 nonlocal。

延迟绑定陷阱

闭包中的自由变量按名称查找,不是按值捕获。这在循环中创建多个闭包时会导致意外行为:

def make_multipliers():
    return [lambda x: i * x for i in range(3)]

multipliers = make_multipliers()
print(multipliers[0](2))   # 4,不是 0!
print(multipliers[1](2))   # 4,不是 2!
print(multipliers[2](2))   # 4

所有闭包共享同一个 i,而 i 在循环结束后是 2。修复方法是利用默认参数在定义时求值的特性:

def make_multipliers_fixed():
    return [lambda x, i=i: i * x for i in range(3)]

multipliers = make_multipliers_fixed()
print(multipliers[0](2))   # 0
print(multipliers[1](2))   # 2
print(multipliers[2](2))   # 4

常见错误

误以为函数可以修改全局变量而不声明:

count = 0

def increment():
    count += 1      # UnboundLocalError
    return count

在循环中创建闭包时忽视延迟绑定:

handlers = []
for i in range(3):
    handlers.append(lambda: print(i))

for h in handlers:
    h()   # 全部打印 2

小结

Python 按 LEGB 顺序解析名称:局部、外层、全局、内置。函数内赋值默认创建局部变量,可能遮蔽外层同名变量。闭包让内层函数记住外层环境,是函数式编程和装饰器的核心机制。理解作用域规则和闭包陷阱,是写出可靠嵌套函数的关键。

上一页
文档字符串与注解
下一页
global 与 nonlocal