> 技术文档 > Python中的global和nonlocal关键字的用法详解

Python中的global和nonlocal关键字的用法详解

在 Python 中,globalnonlocal 关键字用于在函数内部访问和修改外部作用域的变量。它们解决了函数内部无法直接修改外部变量的问题。

1. global 关键字

global 用于在函数内部访问和修改全局作用域(模块级别)的变量。

使用场景:

nonlocal 用于在嵌套函数中访问和修改外层(非全局)作用域的变量。

使用场景:

2. nonlocal 关键字

  • 当需要在函数内部修改全局变量时

  • 当需要在函数内部创建新的全局变量时

  • # 全局变量count = 0def increment(): # 声明 count 是全局变量 global count count += 1 print(f\"函数内部: count = {count}\")print(f\"函数调用前: count = {count}\") # 输出: 函数调用前: count = 0increment() # 输出: 函数内部: count = 1print(f\"函数调用后: count = {count}\") # 输出: 函数调用后: count = 1# 在函数内部创建全局变量def create_global(): global new_var new_var = \"我是全局变量\"create_global()print(new_var) # 输出: 我是全局变量

    注意事项:

  • 在函数内部读取全局变量时,不需要使用 global 关键字

  • 只有在修改全局变量时才需要使用 global

  • 使用 global 声明后,对该变量的所有操作都会影响全局变量

  • 在闭包中修改外层函数的变量

  • 在多层嵌套函数中修改非全局的外部变量

nonlocal 用于在嵌套函数中访问和修改外层(非全局)作用域的变量。

使用场景:

  • 在闭包中修改外层函数的变量

  • 在多层嵌套函数中修改非全局的外部变量

  • def outer(): # 外层函数变量 counter = 0 message = \"原始消息\" def inner(): # 声明 counter 是外层函数的变量 nonlocal counter, message counter += 1 message = f\"修改后的消息 (计数: {counter})\" print(f\"内部函数: counter = {counter}\") print(f\"调用inner前: counter = {counter}, message = \'{message}\'\") inner() # 输出: 内部函数: counter = 1 print(f\"调用inner后: counter = {counter}, message = \'{message}\'\") return counter, messageresult = outer()# 输出:# 调用inner前: counter = 0, message = \'原始消息\'# 内部函数: counter = 1# 调用inner后: counter = 1, message = \'修改后的消息 (计数: 1)\'print(f\"外部获取: counter = {result[0]}, message = \'{result[1]}\'\")

    多层嵌套示例:

  • def outer(): x = \"outer\" def middle(): nonlocal x x = \"middle\" def inner(): nonlocal x x = \"inner\" print(f\"最内层: x = {x}\") inner() print(f\"中间层: x = {x}\") middle() print(f\"最外层: x = {x}\")outer()# 输出:# 最内层: x = inner# 中间层: x = inner# 最外层: x = inner

    注意事项:

  • nonlocal 只能用于嵌套函数中

  • 变量必须在外层函数中已定义,否则会引发 SyntaxError

  • 不能用于全局作用域(使用 global 替代)

  • 在多层嵌套中,nonlocal 会向上查找最近的外层变量

3. global 与 nonlocal 的区别

特性 global nonlocal 作用域 全局作用域(模块级别) 外层非全局作用域 使用位置 任何函数中 仅在嵌套函数中 变量要求 变量可以不存在 变量必须在外层已定义 创建变量 可以创建新的全局变量 不能创建新变量 查找范围 全局命名空间 最近的封闭作用域

 

4. 常见错误及解决方法

错误1:未声明直接修改

x = 10def func(): x += 1 # UnboundLocalError func()

解决方法:使用 global 声明

x = 10def func(): global x x += 1

错误2:nonlocal 变量未定义

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

解决方法:确保外层函数中已定义该变量

def outer(): x = 10 def inner(): nonlocal x x = 20 inner()

错误3:混淆 global 和 nonlocal

x = 100def outer(): x = 10 def inner(): global x # 错误地使用了 global x = 20 # 修改的是全局 x,而不是 outer 的 x inner() print(\"outer x:\", x) # 输出 10,而不是 20outer()print(\"global x:\", x) # 输出 20

解决方法:正确使用 nonlocal

x = 100def outer(): x = 10 def inner(): nonlocal x # 正确声明 x = 20 inner() print(\"outer x:\", x) # 输出 20outer()print(\"global x:\", x) # 输出 100

5. 最佳实践

  1. 尽量避免使用全局变量:全局变量使代码难以维护和理解,考虑使用类或函数返回值替代

  2. 优先使用返回值:尽量通过函数返回值传递结果,而不是直接修改外部变量

  3. 限制使用范围:当必须修改外部状态时,明确使用 globalnonlocal 并添加注释

  4. 命名区分:全局变量使用全大写命名(如 GLOBAL_VAR)以提高可读性

  5. 闭包替代全局变量:对于需要保持状态的场景,使用闭包比全局变量更安全

 

# 使用闭包替代全局变量的示例def create_counter(): count = 0 def counter(): nonlocal count count += 1 return count return countercounter1 = create_counter()print(counter1()) # 1print(counter1()) # 2counter2 = create_counter()print(counter2()) # 1

总结

globalnonlocal 是 Python 中处理变量作用域的重要关键字:

  • global 用于在函数中访问和修改全局变量

  • nonlocal 用于在嵌套函数中访问和修改外层函数的变量

正确理解和使用这两个关键字,可以帮助你编写更灵活的函数和闭包,同时避免常见的变量作用域错误。在实际编程中,应当谨慎使用这些关键字,优先考虑通过函数参数和返回值来传递数据。

关于id()函数有趣的问题:

def outer(): # 外层函数变量 counter = 0 message = \"原始消息\" print(id(counter)) def inner(): # 声明 counter 是外层函数的变量 nonlocal counter, message counter += 1 message = f\"修改后的消息 (计数: {counter})\" print(f\"内部函数: counter = {counter}\") print(f\"调用inner前: counter = {counter}, message = \'{message}\'\") inner() # 输出: 内部函数: counter = 1 print(f\"调用inner后: counter = {counter}, message = \'{message}\'\") print(id(counter)) return counter, messageresult = outer()# 输出:# 调用inner前: counter = 0, message = \'原始消息\'# 内部函数: counter = 1# 调用inner后: counter = 1, message = \'修改后的消息 (计数: 1)\'print(f\"外部获取: counter = {result[0]}, message = \'{result[1]}\'\")

我们在刚才的代码案例中两处添加了print(id(counter)),虽然我们使用了nonlocal使用函数内的变量counter,但是在两条print(id(counter))输出的结果并不一样,这是为什么呢?

原因分析

  1. 整数是不可变类型

    • Python 中的整数(int)是不可变对象(immutable)

    • 当你执行counter += 1时,实际上创建了一个新的整数对象,而不是修改原对象

  2. 变量重新绑定

    • nonlocal counter确保inner中的counter指向outer中的同一个变量

    • 但当执行counter += 1时,相当于counter = counter + 1

    • 这会将outercounter变量重新绑定到一个新的整数对象

  3. 内存地址变化

    • 第一次打印时,counter指向整数0的内存地址

    • 执行counter += 1后,变量指向整数1的内存地址

    • 两个不同的整数对象有不同的内存地址

# 初始状态counter = 0 # 假设内存地址为 0x1000print(id(counter)) # 输出 0x1000 (指向整数0)# 执行 counter += 1# 实际发生的过程:temp = counter + 1 # 创建新整数1,假设地址为 0x2000counter = temp # 变量重新绑定到新地址print(id(counter)) # 输出 0x2000 (指向整数1)

 

证明它们是同一个变量

虽然内存地址不同,但它们确实是同一个变量名(在相同作用域中):

  1. 变量名不变

    • 两次打印都是访问outer作用域中的counter变量

    • 只是变量指向的值改变了

  2. 作用域验证

    • inner函数中修改后,outer中访问到的值确实变为1

    • 返回值也是修改后的值

对比:使用可变对象

如果我们使用可变对象(如列表),情况就不同了:

def outer(): counter = [0] # 使用列表 print(id(counter)) def inner(): nonlocal counter counter[0] += 1 # 修改列表内容,而不是重新绑定 inner() print(id(counter)) # 相同的内存地址outer()

 

在这个例子中,两次id(counter)输出相同,因为:

  1. 列表是可变对象

  2. 我们只修改了列表内容,没有重新绑定整个变量

  3. 变量仍然指向同一个列表对象

关键结论

  1. nonlocal保证你访问的是同一个变量(同一个作用域中的同名变量)

  2. 当操作不可变对象(如整数、字符串、元组)时:

    • 任何\"修改\"实际上创建新对象

    • 变量被重新绑定到新对象

    • id()输出会改变

  3. 当操作可变对象(如列表、字典、集合)时:

    • 可以原地修改内容

    • 变量保持绑定到同一对象

    • id()输出不变