Python中的全局变量与局部变量
核心概念:作用域(Scope)
在理解变量之前,必须先理解作用域。作用域就是一个变量能够被访问(可被读取和写入)的区域。把它想象成一个房子的不同房间:
- 全局作用域(Global Scope):就像房子的客厅。所有房间的人都能看到客厅里的东西。在 Python 中,这是在所有函数之外的顶层代码区域。
- 局部作用域(Local Scope):就像房子的卧室。只有在卧室里的人才能看到和使用卧室里的东西。在 Python 中,这通常是指一个函数内部的区域。
1. 局部变量 (Local Variables)
定义:
在一个函数内部创建(首次赋值)的变量,就是局部变量。
特性:
- 生命周期:当函数被调用时,局部变量被创建;当函数执行完毕返回时,局部变量就被销毁。
- 访问范围:它只能在其被定义的函数内部被访问。在函数外部尝试访问它会导致
NameError
。
示例:
1 | def my_function(): |
在这个例子中,message
变量只存在于 my_function
的“卧室”里。一旦函数执行结束,这个“卧室”就消失了,message
也随之消失。
2. 全局变量 (Global Variables)
定义:
在所有函数之外(即在脚本的顶层)定义的变量,就是全局变量。
特性:
- 生命周期:从被创建开始,直到整个程序结束,全局变量都存在。
- 访问范围:它可以在程序的任何地方被读取,包括任何函数内部。
示例(读取全局变量):
1 | # 'app_name' 是一个全局变量 |
app_name
就像客厅里的电视,print_app_name
这个函数(卧室)里的人可以探出头来看电视。
3. 修改全局变量:global
关键字
这是一个非常关键且容易混淆的点。
问题:如果你在函数内部尝试修改一个全局变量会发生什么?
1 | count = 0 # 全局变量 |
为什么会报错?
Python 有一个重要规则:当你在函数内部对一个变量进行赋值操作时,Python 会默认这个变量是局部变量。
所以在 increment
函数里,count = ...
这行代码让 Python 认为 count
是一个新的局部变量。但是,在赋值号的右边 ... = count + 1
,它又试图去读取这个(还不存在的)局部变量 count
的值,因此就抛出了 UnboundLocalError
。
解决方案:使用 global
关键字。global
关键字的作用是明确告诉 Python:“我现在要操作的这个变量,不是新的局部变量,而是那个在全局作用域里的变量。”
正确示例:
1 | count = 0 # 全局变量 |
运行结果:
1 | Inside function: 1 |
现在,每次调用 increment()
都会正确地修改全局的 count
变量。
4. LEGB 作用域解析规则
Python 查找一个变量时,会遵循一个顺序,这个顺序被称为 LEGB 规则:
- L (Local):局部作用域。首先在函数内部查找。
- E (Enclosing):闭包函数外的函数作用域。用于嵌套函数,即一个函数包裹着另一个函数。
- G (Global):全局作用域。在模块的顶层查找。
- B (Built-in):内建作用域。Python 预先定义的变量,如
len()
,print()
,str
等。
Python 解释器会从 L -> E -> G -> B 的顺序依次查找,一旦找到,就停止搜索。
5. 嵌套函数和 nonlocal
关键字
当函数嵌套时,就出现了 “Enclosing” 作用域。
问题:如何在内层函数中,修改外层(但非全局)函数的变量?
1 | def outer_function(): |
运行结果:
1 | Inner level: inner |
可以看到,inner_function
并没有修改 outer_function
的 level
变量,而是创建了自己的局部变量。
解决方案:使用 nonlocal
关键字。nonlocal
关键字用于告诉 Python,要操作的变量是**最近的外层(非全局)**作用域中的变量。
正确示例:
1 | def outer_function(): |
运行结果:
1 | Inner level: inner |
这次,outer_function
的 level
变量被成功修改了。
6. 最佳实践与注意事项
尽量避免使用
global
:滥用全局变量会使代码变得难以理解和维护。变量的来源和修改变得不可预测,容易产生“幽灵般的行为”(Spooky Action at a Distance),即一个地方的修改意外地影响了另一个完全不相关的地方。优先使用函数参数和返回值:传递状态的最好方式是通过函数参数和返回值。这使得函数成为一个独立的、可预测的单元。
不推荐的写法 (使用 global):
1
2
3
4
5
6value = 10
def add_five():
global value
value += 5
add_five()
print(value) # 15推荐的写法 (使用参数和返回):
1
2
3
4
5
6def add_five(num):
return num + 5
value = 10
value = add_five(value)
print(value) # 15第二种写法更清晰,
add_five
函数也更通用、更易于测试。全局变量用于常量:使用全局变量来定义常量(程序运行期间不会改变值的变量)是一个很好的实践。按照惯例,常量名通常用全大写字母表示。
1
2
3
4
5PI = 3.14159
API_KEY = "YOUR_SECRET_KEY"
def calculate_area(radius):
return PI * radius * radius
总结
特性 | 局部变量 (Local) | 全局变量 (Global) | 外层变量 (Nonlocal) |
---|---|---|---|
定义位置 | 函数内部 | 所有函数外部 | 外层函数内部 |
作用域 | 定义它的函数内部 | 整个模块/脚本 | 从定义处到嵌套函数结束 |
生命周期 | 函数调用开始到结束 | 程序运行开始到结束 | 外层函数调用开始到结束 |
修改关键字 | 无 | global 变量名 |
nonlocal 变量名 |
最佳实践 | 用于函数内部的临时计算和状态 | 用于定义常量 | 用于编写闭包和装饰器 |