核心概念:作用域(Scope)

在理解变量之前,必须先理解作用域。作用域就是一个变量能够被访问(可被读取和写入)的区域。把它想象成一个房子的不同房间:

  • 全局作用域(Global Scope):就像房子的客厅。所有房间的人都能看到客厅里的东西。在 Python 中,这是在所有函数之外的顶层代码区域。
  • 局部作用域(Local Scope):就像房子的卧室。只有在卧室里的人才能看到和使用卧室里的东西。在 Python 中,这通常是指一个函数内部的区域。

1. 局部变量 (Local Variables)

定义
在一个函数内部创建(首次赋值)的变量,就是局部变量。

特性

  • 生命周期:当函数被调用时,局部变量被创建;当函数执行完毕返回时,局部变量就被销毁。
  • 访问范围:它只能在其被定义的函数内部被访问。在函数外部尝试访问它会导致 NameError

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def my_function():
# 'message' 是一个局部变量,它在 my_function 内部被创建
message = "Hello from inside the function"
print(message)

# 调用函数,函数内部可以正常访问 message
my_function()
# 输出: Hello from inside the function

# 尝试在函数外部访问 message
try:
print(message)
except NameError as e:
print(e)
# 输出: name 'message' is not defined

在这个例子中,message 变量只存在于 my_function 的“卧室”里。一旦函数执行结束,这个“卧室”就消失了,message 也随之消失。


2. 全局变量 (Global Variables)

定义
在所有函数之外(即在脚本的顶层)定义的变量,就是全局变量。

特性

  • 生命周期:从被创建开始,直到整个程序结束,全局变量都存在。
  • 访问范围:它可以在程序的任何地方被读取,包括任何函数内部。

示例(读取全局变量):

1
2
3
4
5
6
7
8
9
10
11
12
# 'app_name' 是一个全局变量
app_name = "My Awesome App"

def print_app_name():
# 在函数内部,我们可以直接读取全局变量
print("Welcome to", app_name)

print_app_name()
# 输出: Welcome to My Awesome App

print("The application is called:", app_name)
# 输出: The application is called: My Awesome App

app_name 就像客厅里的电视,print_app_name 这个函数(卧室)里的人可以探出头来看电视。


3. 修改全局变量:global 关键字

这是一个非常关键且容易混淆的点。

问题:如果你在函数内部尝试修改一个全局变量会发生什么?

1
2
3
4
5
6
7
8
9
10
11
12
count = 0  # 全局变量

def increment():
# 我们想让全局的 count 增加 1
count = count + 1 # 这一行会报错!
print("Inside function:", count)

try:
increment()
except UnboundLocalError as e:
print(e)
# 输出: local variable 'count' referenced before assignment

为什么会报错?
Python 有一个重要规则:当你在函数内部对一个变量进行赋值操作时,Python 会默认这个变量是局部变量。

所以在 increment 函数里,count = ... 这行代码让 Python 认为 count 是一个新的局部变量。但是,在赋值号的右边 ... = count + 1,它又试图去读取这个(还不存在的)局部变量 count 的值,因此就抛出了 UnboundLocalError

解决方案:使用 global 关键字。
global 关键字的作用是明确告诉 Python:“我现在要操作的这个变量,不是新的局部变量,而是那个在全局作用域里的变量。”

正确示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
count = 0  # 全局变量

def increment():
# 告诉 Python,我们要修改的是全局变量 count
global count
count = count + 1
print("Inside function:", count)

increment()
print("Outside function:", count)

increment()
print("Outside function:", count)

运行结果:

1
2
3
4
Inside function: 1
Outside function: 1
Inside function: 2
Outside function: 2

现在,每次调用 increment() 都会正确地修改全局的 count 变量。


4. LEGB 作用域解析规则

Python 查找一个变量时,会遵循一个顺序,这个顺序被称为 LEGB 规则

  1. L (Local):局部作用域。首先在函数内部查找。
  2. E (Enclosing):闭包函数外的函数作用域。用于嵌套函数,即一个函数包裹着另一个函数。
  3. G (Global):全局作用域。在模块的顶层查找。
  4. B (Built-in):内建作用域。Python 预先定义的变量,如 len(), print(), str 等。

Python 解释器会从 L -> E -> G -> B 的顺序依次查找,一旦找到,就停止搜索。


5. 嵌套函数和 nonlocal 关键字

当函数嵌套时,就出现了 “Enclosing” 作用域。

问题:如何在内层函数中,修改外层(但非全局)函数的变量?

1
2
3
4
5
6
7
8
9
10
11
12
def outer_function():
level = "outer"

def inner_function():
# 我们想修改外层函数的 level 变量
level = "inner" # 这实际上创建了一个新的、属于 inner_function 的局部变量
print("Inner level:", level)

inner_function()
print("Outer level:", level)

outer_function()

运行结果:

1
2
Inner level: inner
Outer level: outer

可以看到,inner_function 并没有修改 outer_functionlevel 变量,而是创建了自己的局部变量。

解决方案:使用 nonlocal 关键字。
nonlocal 关键字用于告诉 Python,要操作的变量是**最近的外层(非全局)**作用域中的变量。

正确示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
def outer_function():
level = "outer"

def inner_function():
# 告诉 Python,我们要修改的是外层函数的 level 变量
nonlocal level
level = "inner"
print("Inner level:", level)

inner_function()
print("Outer level:", level)

outer_function()

运行结果:

1
2
Inner level: inner
Outer level: inner

这次,outer_functionlevel 变量被成功修改了。


6. 最佳实践与注意事项

  1. 尽量避免使用 global:滥用全局变量会使代码变得难以理解和维护。变量的来源和修改变得不可预测,容易产生“幽灵般的行为”(Spooky Action at a Distance),即一个地方的修改意外地影响了另一个完全不相关的地方。

  2. 优先使用函数参数和返回值:传递状态的最好方式是通过函数参数和返回值。这使得函数成为一个独立的、可预测的单元。

    不推荐的写法 (使用 global):

    1
    2
    3
    4
    5
    6
    value = 10
    def add_five():
    global value
    value += 5
    add_five()
    print(value) # 15

    推荐的写法 (使用参数和返回):

    1
    2
    3
    4
    5
    6
    def add_five(num):
    return num + 5

    value = 10
    value = add_five(value)
    print(value) # 15

    第二种写法更清晰,add_five 函数也更通用、更易于测试。

  3. 全局变量用于常量:使用全局变量来定义常量(程序运行期间不会改变值的变量)是一个很好的实践。按照惯例,常量名通常用全大写字母表示。

    1
    2
    3
    4
    5
    PI = 3.14159
    API_KEY = "YOUR_SECRET_KEY"

    def calculate_area(radius):
    return PI * radius * radius

总结

特性 局部变量 (Local) 全局变量 (Global) 外层变量 (Nonlocal)
定义位置 函数内部 所有函数外部 外层函数内部
作用域 定义它的函数内部 整个模块/脚本 从定义处到嵌套函数结束
生命周期 函数调用开始到结束 程序运行开始到结束 外层函数调用开始到结束
修改关键字 global 变量名 nonlocal 变量名
最佳实践 用于函数内部的临时计算和状态 用于定义常量 用于编写闭包和装饰器