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

    • 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章 工程实践

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

global 与 nonlocal

当函数需要修改外层作用域的变量时,单纯的赋值操作会创建局部变量,导致 UnboundLocalError。global 和 nonlocal 两个关键字就是用来打破这一限制的:它们告诉 Python,某个名称不是局部变量,而是外层命名空间中的已有变量。

global 语法与作用

global 声明把变量标记为全局变量,函数内的赋值会直接修改模块级别的全局命名空间:

counter = 0

def increment():
    global counter
    counter += 1
    return counter

print(increment())   # 1
print(increment())   # 2
print(counter)         # 2

没有 global 声明时,counter += 1 会被编译为"读取局部变量 counter 并加 1 后写回",但局部命名空间中还没有 counter,所以抛出 UnboundLocalError。

global 可以在函数内任何位置声明,但习惯上放在函数开头。声明后,该名称在函数内任何地方都指向全局变量:

x = "global"

def demo():
    print(x)        # 这里还没声明 global,但只是读取,所以去全局找
    global x        # 声明后,x 指向全局
    x = "modified"

demo()
print(x)            # modified

注意:global 声明必须在赋值之前,但可以在读取之后。不过为了可读性,建议把 global 放在函数体最前面。

global 的使用场景

global 适合以下场景:

  1. 模块级别的状态计数器:如上述 counter 示例。
  2. 配置标志:模块级别的开关变量,多个函数共同修改。
  3. 缓存:函数间共享的缓存字典。
_cache = {}

def get_data(key):
    global _cache
    if key not in _cache:
        _cache[key] = expensive_computation(key)
    return _cache[key]

但过度使用 global 会导致代码难以追踪和测试。更好的做法是把状态封装到类中,或使用闭包。global 应作为最后手段,而非首选方案。

nonlocal 语法与作用

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

nonlocal 与 global 的关键区别:

  • global 指向模块全局命名空间
  • nonlocal 指向最近的外层函数命名空间(不包括全局)

如果外层没有该名称,nonlocal 会报错:

def outer():
    def inner():
        nonlocal x      # SyntaxError: no binding for nonlocal 'x' found
        x = 1
    inner()

outer()

nonlocal 与多层嵌套

nonlocal 会逐层向外查找,直到找到第一个匹配的名称:

def level1():
    x = "level1"
    
    def level2():
        x = "level2"
        
        def level3():
            nonlocal x
            x = "level3"    # 修改的是 level2 的 x,不是 level1 的
        
        level3()
        print(x)            # level3
    
    level2()
    print(x)                # level1

level1()

如果需要在最内层修改最外层的变量,而中间层也有同名变量,Python 没有直接机制跳过中间层。这种情况下应考虑重构,避免同名变量造成混淆。

常见错误

用 global 代替 nonlocal:

def outer():
    x = "outer"
    
    def inner():
        global x        # 错误!这会创建/修改全局变量 x,不是 outer 的 x
        x = "global"
    
    inner()
    print(x)            # outer(outer 的 x 未被修改)

outer()
print(x)                # global(全局命名空间被污染)

忘记声明就修改外层变量:

def outer():
    count = 0
    
    def inner():
        count += 1      # UnboundLocalError
        return count
    
    return inner

试图用 nonlocal 修改全局变量:

x = "global"

def demo():
    def inner():
        nonlocal x      # SyntaxError: no binding for nonlocal 'x' found
        x = "inner"
    inner()

demo()

nonlocal 不能用于全局变量,全局变量必须用 global。

与闭包的关系

nonlocal 是闭包修改状态的标准方式。没有 nonlocal,闭包只能读取外层变量,不能修改:

def make_accumulator():
    total = 0
    
    def add(n):
        # 没有 nonlocal,只能读取 total
        return total + n   # 可以读取,但无法累积
    
    return add

acc = make_accumulator()
print(acc(10))   # 10
print(acc(5))    # 5,不是 15!因为没有修改 total

加上 nonlocal 后,闭包才能真正"记住"并更新状态:

def make_accumulator():
    total = 0
    
    def add(n):
        nonlocal total
        total += n
        return total
    
    return add

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

可变对象的替代方案

如果外层变量是可变对象(列表、字典),可以通过原地修改避免使用 nonlocal:

def make_counter():
    count = [0]          # 用列表包装
    
    def counter():
        count[0] += 1    # 原地修改,不是重新绑定
        return count[0]
    
    return counter

这种技巧在某些场景下可以简化代码,但语义不如 nonlocal 清晰。推荐优先使用 nonlocal,让意图更明确。

小结

global 声明变量为全局,允许函数修改模块级变量;nonlocal 声明变量为外层局部,允许嵌套函数修改 enclosing 作用域的变量。两者都解决"赋值创建局部变量"的问题,但指向不同的命名空间。合理使用它们可以实现状态持久化,但过度依赖会损害代码的模块化。优先考虑参数传递、返回值和类封装,必要时再用 global 和 nonlocal。

上一页
作用域与命名空间