global和nonlocal到底有什么区别?

简介: 本文以一次代码审查“社死”经历切入,深入剖析Python中`global`与`nonlocal`的关键区别:前者用于修改模块级全局变量,后者专用于嵌套函数中修改外层函数的局部变量。通过对比示例、典型场景、易错点及速查口诀,帮你彻底理清用法,告别混淆。

一个让我在代码审查时社死的经历

去年公司做代码审查,我提交了一段这样的代码:

counter = 0

def increment():
   counter += 1
   return counter

def outer():
   x = 10
   
   def inner():
       x += 1
       return x
   
   return inner()

审查官看了一眼,说:“你这两个函数都会报错,知道为什么吗?”

我当时脑子一热,脱口而出:“我知道,第一个要加global,第二个要加nonlocal。”

“那你加了没?”

“呃……忘了。”

审查官笑了笑:“你这不是忘了,你是分不清什么时候用global,什么时候用nonlocal。”

他说得对。我当时确实分不清。这两个关键字看起来差不多,都是在函数里声明“这个变量不是我本地的”,但具体怎么用,脑子里是一团浆糊。

今天我就把这两个关键字的区别彻底讲清楚。看完之后,你绝对不会再搞混。

代理 IP 使用小技巧 让你的数据抓取效率翻倍 (22).png


先看一个最简单的对比

# global 的例子
x = 10  # 全局变量

def func1():
   global x
   x = 20  # 修改全局的x

func1()
print(x)  # 20


# nonlocal 的例子
def outer():
   y = 10  # 外层函数的变量
   
   def inner():
       nonlocal y
       y = 20  # 修改外层函数的y
   
   inner()
   print(y)  # 20

outer()

从代码上看,区别很明显:

  • global用在函数内部,目标是全局作用域的变量
  • nonlocal用在嵌套函数内部,目标是外层函数的变量

但光看这个还不够。我们深入拆解一下。


global:走出函数,走到文件最外层

先看一个会报错的例子:

name = "张三"

def change():
   name = "李四"   # 这是创建了一个新的局部变量,不是修改全局的

change()
print(name)  # 输出"张三"——没变!

上面的代码不会报错,但是达不到你想要的效果。因为name = "李四"在函数内部创建了一个同名的局部变量,外面的name根本没动过。

如果加上global

name = "张三"

def change():
   global name
   name = "李四"   # 现在修改的是全局变量

change()
print(name)  # 输出"李四"

global的作用就是告诉Python:“别在函数里创建新变量,直接用外面的那个。”

global的核心规则:

  1. 只能在函数内部使用(模块顶层不需要,因为顶层本来就是全局作用域)
  2. 声明的变量名必须是全局作用域里已经存在的,或者你准备在全局创建它
  3. 一个函数里可以有多个global声明:global a, b, c

global的典型应用场景

场景1:修改配置变量

DEBUG = False

def enable_debug():
   global DEBUG
   DEBUG = True

def disable_debug():
   global DEBUG
   DEBUG = False

场景2:计数器

call_count = 0

def track_call():
   global call_count
   call_count += 1
   print(f"这个函数被调用了{call_count}次")

场景3:在函数内部创建全局变量

def create_global():
   global new_var
   new_var = "我是在函数里创建的全局变量"

create_global()
print(new_var)  # 正常输出

这种情况比较少见,但语法上是允许的。

global 容易踩的坑

坑1:在global声明之前使用变量

x = 10

def bad():
   print(x)   # 这里想读全局x
   global x   # 但是global声明应该在前面
   x = 20

bad()

这会报语法错误:SyntaxError: name 'x' is used prior to global declaration

正确写法:global声明要放在函数的最前面(在用到这个变量之前)。

坑2:在for/if里用global

x = 10

def func():
   for i in range(3):
       global x  # 语法上可以,但没必要放在循环里
       x = i

func()
print(x)  # 2

global声明应该放在函数顶部,不要写在循环或条件语句里。虽然语法允许,但会让人困惑。

坑3:以为global可以跨文件

# file1.py
x = 10

# file2.py
from file1 import x

def change():
   global x  # 这个x并不是file1里的x!
   x = 20

global只在当前模块(当前文件)的全局作用域里生效。如果你从别的模块导入了一个变量,修改它不会影响原模块。

跨文件共享状态应该用模块对象(import file1; file1.x = 20),而不是global


nonlocal:走出内层函数,但只走到外层函数

nonlocal是Python 3才引入的。在Python 2里,嵌套函数想修改外层函数的变量,只能把变量做成列表或字典来绕过限制。

看一个会报错的例子:

def outer():
   count = 0
   
   def inner():
       count += 1   # 报错!count被视为inner的局部变量
       return count
   
   return inner()

加上nonlocal

def outer():
   count = 0
   
   def inner():
       nonlocal count   # 声明:这个count来自外层函数
       count += 1
       return count
   
   return inner()

print(outer())  # 1

nonlocal的核心规则:

  1. 只能在嵌套函数(函数里面定义的函数)内部使用
  2. 声明的变量必须在外层函数的局部作用域里已经存在
  3. 不能用来声明全局变量(会报语法错误)
  4. 不能跳过外层直接修改更外层的变量,它只找最近的一层

nonlocal的典型应用场景

场景1:闭包里的计数器

def make_counter():
   count = 0
   
   def increment():
       nonlocal count
       count += 1
       return count
   
   return increment

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

这是闭包的标准写法:外层函数创建一个变量,内层函数通过nonlocal修改它。

场景2:装饰器里维护状态

def count_calls(func):
   call_count = 0
   
   def wrapper(*args, **kwargs):
       nonlocal call_count
       call_count += 1
       print(f"{func.__name__}被调用了{call_count}次")
       return func(*args, **kwargs)
   
   return wrapper

@count_calls
def say_hello():
   print("你好")

say_hello()  # say_hello被调用了1次
say_hello()  # say_hello被调用了2次

场景3:多层嵌套

def outer():
   x = "outer"
   
   def middle():
       x = "middle"  # 这是middle的局部变量
       
       def inner():
           nonlocal x  # 这个x指向谁?
           x = "inner"
       
       inner()
       print(x)  # 输出"inner"
   
   middle()
   print(x)  # 输出"outer"——外层的x没有被影响

outer()

nonlocal查找规则:从当前函数(inner)的直接外层开始找,找到的第一个同名变量就是目标。这里找到的是middle里的x,不是outer里的。

如果middle里没有定义xnonlocal会继续往outer里找。如果都找不到,报语法错误。


一张对比表,一目了然

特性 global nonlocal
用在哪里 任何函数内部 仅限嵌套函数内部
目标作用域 全局作用域 外层函数的局部作用域
目标变量必须存在? 否(可以在函数里创建新的全局变量) 是(必须已经在外层函数里定义)
能用在模块顶层吗? 不能(顶层不需要) 不能(顶层没有外层函数)
能跨文件吗? 不能(只在当前模块有效) 不适用
Python版本 所有版本 Python 3+

三个容易混淆的细节

细节1:nonlocal不能声明不存在的变量

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

nonlocal要求变量已经存在于外层作用域。这和global不同,global可以在函数里创建新的全局变量。

细节2:在同一个作用域里,global和nonlocal不能混用

x = 10

def outer():
   x = 20
   
   def inner():
       global x   # 指向全局的x(值为10)
       nonlocal x  # SyntaxError! 不能同时声明

一个变量要么是全局的,要么是外层函数的,不能同时是两者。

细节3:nonlocal只能往上找一层吗?

很多人以为nonlocal只能找直接外层,其实不是。它会一直往上找,直到找到最近的匹配:

def outer():
   x = "outer"
   
   def middle():
       # middle没有定义x
       
       def inner():
           nonlocal x  # 从middle开始找,找不到,继续往外找,找到outer里的x
           x = "inner changed"
       
       inner()
   
   middle()
   print(x)  # "inner changed"

outer()

nonlocal会沿着嵌套层次一层层往上找,直到找到目标变量。但如果到了全局还没找到,就会报错(nonlocal不会到全局去找)。


实战:用闭包造一个“带状态的函数”

这是一个结合了nonlocal的经典例子:

def create_account(initial_balance=0):
   balance = initial_balance
   transactions = []
   
   def deposit(amount):
       nonlocal balance
       balance += amount
       transactions.append(f"存入{amount}")
       return balance
   
   def withdraw(amount):
       nonlocal balance
       if amount > balance:
           raise ValueError("余额不足")
       balance -= amount
       transactions.append(f"取出{amount}")
       return balance
   
   def get_balance():
       return balance
   
   def get_transactions():
       return transactions.copy()
   
   return deposit, withdraw, get_balance, get_transactions

deposit, withdraw, get_balance, get_transactions = create_account(100)
deposit(50)      # 150
withdraw(30)     # 120
print(get_balance())      # 120
print(get_transactions()) # ['存入50', '取出30']

这里用nonlocaldepositwithdraw能修改外层函数的balance变量。

如果不加nonlocalbalance += amount会在deposit内部创建局部变量balance,外面的balance不会变。


为什么Python要分这两个关键字?

一个常见的问题是:“为什么不统一用outer或者scope之类的关键字,非要分globalnonlocal?”

原因有两个:

1. 语义不同

global是从当前作用域直接跳到模块顶层,是一种“跳跃式”的访问。nonlocal是沿着嵌套关系逐层往上找,是一种“爬楼梯式”的访问。

这两种行为不一样,用不同的关键字能清晰地表达意图。

2. 安全性

nonlocal不能用于全局变量,这防止了你在嵌套函数里无意中修改了全局状态。如果你确实想改全局变量,必须明确使用global

这种设计强迫程序员显式地声明“我知道这个变量是共享的,我负责”。


快速判断该用哪个

如果你需要修改一个变量,问自己三个问题:

问题1:这个变量在哪里定义的?

  • 在函数外面定义的 → 用global
  • 在外层函数里定义的 → 用nonlocal
  • 在内层函数里定义的 → 不需要任何关键字(它就是局部变量)

问题2:我现在在哪?

  • 在一个普通函数里 → 只能选global(如果确实需要改全局变量)
  • 在一个嵌套函数里 → 可能用global也可能用nonlocal,取决于目标变量在哪

问题3:如果不确定,先不加关键字,看报错信息

  • 报错说local variable referenced before assignment → 需要用globalnonlocal
  • 报错说no binding for nonlocal → 说明变量不在外层函数里,试试global
  • 报错说no binding for global → 说明变量不在全局里,试试nonlocal

一个终极测试

猜猜下面这段代码的输出是什么?

x = "global"

def outer():
   x = "outer"
   
   def middle():
       x = "middle"
       
       def inner():
           nonlocal x
           x = "inner"
           print(f"inner里: {x}")
       
       inner()
       print(f"middle里: {x}")
   
   middle()
   print(f"outer里: {x}")

outer()
print(f"全局: {x}")

答案:

inner里: inner
middle里: inner
outer里: outer
全局: global

原因:

  • inner里的nonlocal x找到了middle里的x(最近的外层),把它改成"inner"
  • outer里的x没有被影响,因为nonlocal只往上找到最近的那一层就停了
  • 全局的x完全不受影响

如果你能完全说清楚这个输出,说明你已经彻底掌握了globalnonlocal的区别。


最后一句总结

  • global:我要修改全局变量,跟当前函数之外的文件顶层对话
  • nonlocal:我要修改外层函数的变量,跟外层的函数对话

记住这个简单的场景选择:如果变量在函数外面(文件顶层),用global。如果变量在外层函数里,用nonlocal

就这一句话,够用了。

目录
相关文章
|
5天前
|
缓存 测试技术 API
Qwen 3.7 Plus 与 Max 实测:性价比与多模态能力差异解析(2026)
2026 年 6 月 1 日,阿里悄无声息地发布了 Qwen 3.7 Plus,距 Qwen 3.7 Max 上线刚好 11 天。同样的 1M 上下文,同样的 35 小时自治上限。但价格才是头条:Plus 是 0.40/M输入,Max是 2.50/M——便宜约 6 倍——并且还能看图、看视频。Vision Arena 上 Plus 已经排到 #16。所以这周真正值得讨论的问题不是”要不要为视觉能力买单”,而是”Max 凭什么用 6 倍价格换来 2 个百分点的 benchmark 领先”。
|
6天前
|
JavaScript 定位技术 API
CodeGraph 爆火:编程 Agent 需要的不是更多上下文,而是一张提前画好的代码地图
CodeGraph 是一款爆火的本地代码智能工具,通过 tree-sitter 解析 AST 构建结构化知识图谱(存于 SQLite),为编程 Agent 提前生成“代码地图”。它显著降低 Agent 在中大型项目中的探索成本——实测工具调用减少71%、Token 降57%、速度提升46%,支持19+语言及主流框架路由识别,完全离线、无需 API Key。
696 5
CodeGraph 爆火:编程 Agent 需要的不是更多上下文,而是一张提前画好的代码地图
|
6天前
|
人工智能 自然语言处理 文字识别
阿里云百炼Qwen3.7-Max简介:能力、优势、支持订阅计划参考
Qwen3.7-Max是阿里云百炼面向智能体时代推出的新一代旗舰模型,对标GPT-5.5、Claude Opus 4.7等闭源旗舰。该模型支持百万级token上下文窗口,具备顶级推理能力、多模态搜索与视觉理解增强、流式输出低延迟响应等核心优势,覆盖编程、办公、长周期自主执行等复杂场景。同时支持OpenAI接口兼容,便于系统快速迁移。用户可通过Token Plan团队或节省计划等订阅方式灵活调用,适合企业级高要求场景使用。
8721 37
阿里云百炼Qwen3.7-Max简介:能力、优势、支持订阅计划参考
|
6天前
|
人工智能 运维 JavaScript
阿里云Qoder CN(原通义灵码)全解析 产品形态、版本划分与技术适配说明
在AI辅助开发与智能办公工具持续普及的当下,阿里云旗下原通义灵码正式更名为Qoder CN,同时延伸出QoderWork CN、Qoder CN CLI、Qoder CN Mobile等多款配套产品,形成覆盖代码开发、日常办公、终端交互、移动端使用的完整工具矩阵。Qoder CN核心定位为AI智能编码助手,深度适配主流代码编辑器、集成开发环境以及终端场景;QoderWork CN则偏向桌面端综合办公辅助,二者面向不同使用场景,划分了多个版本档位,搭配差异化资源配额、功能权限与计费规则,同时兼容多款主流大模型。
687 5
|
6天前
|
存储 安全 Java
AgentScope Java 2.0:打造分布式、企业级智能体底座
AgentScope 2.0 面向分布式部署、稳定运行、权限安全等企业级需求全面升级,打造支持多租户隔离与长期稳定运行的企业级智能体底座。
|
6天前
|
数据采集 人工智能 前端开发
让 Coding Agent 从黑盒到透明:阿里云 Agent 观测审计数据采集实践
AI Agent 规模化落地带来执行黑盒、行为难追溯、成本难度量三大难题。阿里云基于 OTel 标准,面向 Coding Agent、个人通用助理和框架型 Agent,推出 LoongSuite Pilot、插件及探针等无侵入采集方案,让 Agent 实现可看见、可分析、可审计、可治理。
744 148
|
6天前
|
人工智能 运维 自然语言处理
阿里云百炼Qwen3.7-Max模型详解:综合能力、核心优势与订阅计划参考指南
2026年,大模型技术持续向通用化、高性能、场景化方向迭代,阿里云百炼作为一站式大模型服务平台,持续推出迭代升级的模型产品,Qwen3.7-Max便是当前主力旗舰级大模型之一。该模型依托深度优化的底层架构与大规模训练数据,在文本理解、逻辑推理、多模态交互、代码生成、长文本处理等多个维度实现能力升级,同时搭配灵活的订阅计划体系,能够适配个人开发者、中小企业、大型企业、政企机构等不同类型用户的使用需求。
579 2
|
6天前
|
JSON 缓存 安全
通过 CC Switch 本地路由让 Codex CLI 接入 DeepSeek 等第三方模型
CC Switch 通过本地路由(`127.0.0.1:15721`)实现协议转换:将 Codex 的 Responses API 请求自动映射为 DeepSeek 等厂商的 Chat Completions 接口,兼容流式响应与工具调用,无需修改 Codex 源码,安全隔离 API Key。(239字)
1750 3
通过 CC Switch 本地路由让 Codex CLI 接入 DeepSeek 等第三方模型
|
6天前
|
人工智能 缓存 自然语言处理
阿里Qwen3.7-Max评测:Agent能力显著提升,耗时与调用成本大幅下降
阿里云百炼推出面向智能体的旗舰大模型Qwen3.7-Max,具备长周期自主执行能力,显著提升编程、办公自动化等复杂任务处理水平;支持MCP集成与多框架兼容,并以限时5折+100万Tokens免费试用大幅降低使用门槛,助力企业高效落地AI应用。在阿里云百炼平台快速体验:https://t.aliyun.com/U/fPVHqY
1971 10
|
6天前
|
人工智能 运维 API
2026年阿里云百炼通义千问Qwen3.7-plus深度介绍 功能特性、使用优势及618大促订阅方案指南
大模型技术的普及,让AI能力逐步融入个人办公、内容创作、代码编写、企业运营、教育培训等各类场景。不同定位的模型对应不同使用需求,旗舰级模型性能强劲但使用成本偏高,轻量化模型价格低廉却难以胜任复杂任务,而介于两者之间的中端主力模型,凭借均衡的能力、亲民的定价、广泛的场景适配性,成为绝大多数个人用户、小型团队、中小企业的首选。
794 1