一、什么是变量作用域?
变量作用域 是指变量在程序中可以被访问的区域。简单来说,就是“这个变量在哪些地方能用,在哪些地方不能用”。理解作用域是避免变量冲突、写出正确代码的关键。
deffunc():
x = 10# x 在函数内部定义
func()
print(x) # NameError: name 'x' is not defined
上面的例子中,x 是在函数内部定义的,在函数外部无法访问,这就是作用域的限制。
二、Python 中的作用域规则
Python 中变量的作用域遵循 LEGB 规则(后面会详细解释)。主要有以下几种作用域:
- • 局部作用域(Local):函数内部定义的变量。
- • 嵌套作用域(Enclosing):嵌套函数中,外层函数变量对内层函数的作用域。
- • 全局作用域(Global):模块级别(文件顶层)定义的变量。
- • 内置作用域(Built-in):Python 内置的模块、函数等。
三、局部变量
在函数内部定义的变量称为局部变量。它们只在函数内部可见,函数执行结束后,这些变量就会被销毁。
defgreet():
message = "你好"# 局部变量
print(message)
greet() # 输出:你好
print(message) # NameError: name 'message' is not defined
特点:
- • 不同函数的局部变量互不影响,即使同名也没有关系。
deffunc1():
x = 5
print("func1:", x)
deffunc2():
x = 10
print("func2:", x)
func1() # func1: 5
func2() # func2: 10
四、全局变量
在函数外部、模块顶层定义的变量称为全局变量。它们在整个模块中都可以访问(包括函数内部)。
name = "张三"# 全局变量
defshow():
print(name) # 可以在函数内部读取
show() # 输出:张三
print(name) # 输出:张三
读取全局变量是允许的,但修改全局变量需要特殊处理。
五、global 关键字——在函数内修改全局变量
在函数内部,如果直接给一个变量赋值,Python 会默认创建一个新的局部变量,而不是修改同名的全局变量。
count = 0
defincrement():
count = count + 1# 这里会报错 UnboundLocalError
increment()
为什么会报错?因为 Python 在编译函数时,看到 count = count + 1,认为 count 是局部变量(因为它出现在赋值语句左侧),但在计算 count + 1 时,这个局部变量还没有定义,所以出错。
要修改全局变量,必须使用 global 关键字声明。
count = 0
defincrement():
global count # 声明 count 是全局变量
count = count + 1
increment()
print(count) # 1
注意: 使用 global 时要小心,过度使用可能导致代码难以理解和维护。通常建议尽量少用全局变量,通过参数传递和返回值来沟通。

六、嵌套函数与 nonlocal 关键字
Python 允许在函数内部再定义函数(嵌套函数)。这时就出现了嵌套作用域:内层函数可以访问外层函数的变量,但不能修改(除非使用 nonlocal)。
defouter():
x = 10
definner():
print(x) # 可以读取外层变量
inner()
outer() # 输出 10
如果内层函数想要修改外层函数的变量,需要使用 nonlocal 关键字。
defouter():
x = 10
definner():
nonlocal x
x = 20# 修改外层变量
inner()
print(x) # 20
outer()
nonlocal 的作用是告诉 Python,这个变量不是局部变量,而是来自外层嵌套函数的变量(但不是全局变量)。
七、变量查找规则——LEGB
当在函数内部使用一个变量时,Python 按照以下顺序查找变量:
- 2. E(Enclosing):外层嵌套函数(如果有)的变量。
- 4. B(Built-in):Python 内置的变量(如
print、len 等)。
x = "全局"
defouter():
x = "外层"
definner():
x = "内层"
print(x)
inner()
outer() # 输出:内层
注释掉内层的 x 赋值:
defouter():
x = "外层"
definner():
print(x) # 找不到局部 x,找外层,找到 "外层"
inner()
outer() # 输出:外层
再注释掉外层的 x:
x = "全局"
defouter():
definner():
print(x) # 找不到局部和外层,找全局
inner()
outer() # 输出:全局
如果所有地方都找不到,则引发 NameError。
八、常见陷阱与注意事项
8.1 在函数内修改可变对象(如列表)不需要 global
如果变量是可变对象(列表、字典等),修改对象的内容(如添加元素)并不需要 global 声明,因为没有给变量本身重新赋值(即没有改变变量的引用)。
items = [1, 2, 3]
defadd_item(x):
items.append(x) # 修改列表内容,不需要 global
add_item(4)
print(items) # [1, 2, 3, 4]
但如果你试图给 items 重新赋值(如 items = [5,6,7]),则会产生一个新的局部变量,此时若要修改全局变量需用 global。
8.2 在函数内使用全局变量名作为局部变量需谨慎
如果函数内有一个局部变量和全局变量同名,局部变量会屏蔽全局变量。
x = 10
deftest():
x = 20# 局部变量,与全局无关
print(x)
test() # 20
print(x) # 10
8.3 不要在函数内部随意修改全局变量
过多依赖全局变量会让函数变得不纯粹,难以测试和维护。推荐通过参数传递和返回值来交换数据。
8.4 嵌套作用域与 lambda 的坑
在使用 lambda 或循环创建嵌套函数时,要注意变量捕获的时机。
funcs = []
for i inrange(3):
funcs.append(lambda: i) # i 是外层函数的变量
for f in funcs:
print(f()) # 输出 2 2 2,因为循环结束后 i 为 2
可以使用默认参数来“固定”当前值:
funcs = []
for i inrange(3):
funcs.append(lambda x=i: x) # 用默认参数绑定当前 i
九、实战案例
案例1:统计函数调用次数
count = 0
defcall_counter():
global count
count += 1
print(f"函数被调用了 {count} 次")
call_counter() # 函数被调用了 1 次
call_counter() # 函数被调用了 2 次
更好的实现是用闭包(避免全局变量):
defmake_counter():
count = 0
defcounter():
nonlocal count
count += 1
print(f"调用次数:{count}")
return counter
c = make_counter()
c() # 调用次数:1
c() # 调用次数:2
案例2:配置管理
config = {
"host": "localhost",
"port": 8080
}
defget_config(key):
return config.get(key)
defset_config(key, value):
global config # 如果需要修改整个 config 引用
config[key] = value # 修改内容无需 global
set_config("port", 9090)
print(get_config("port")) # 9090
案例3:递归函数中避免使用全局变量
递归函数往往用参数传递状态,而不是依赖全局变量。
deffactorial(n):
if n == 0:
return1
return n * factorial(n-1)
案例4:使用 nonlocal 实现累加器
defouter():
total = 0
defadd(x):
nonlocal total
total += x
return total
return add
adder = outer()
print(adder(5)) # 5
print(adder(3)) # 8
print(adder(2)) # 10
十、总结
- • 要修改外层嵌套函数的变量,使用
nonlocal 声明。 - • 理解 LEGB 规则,能帮你快速定位变量问题。
掌握变量作用域,是编写清晰、健壮代码的重要一步。希望本文能帮你理清这些概念,写出更优雅的 Python 函数。