LEGB 规则的核心定义
L - Local(局部作用域)
- 定义:函数内部定义的变量
- 范围:仅在当前函数体内有效
- 特点:函数调用时创建,函数返回时销毁
E - Enclosing(嵌套作用域)
- 定义:外部嵌套函数中定义的变量
- 范围:在嵌套函数结构中,内层函数可以访问外层函数的变量
- 特点:形成了闭包的基础
G - Global(全局作用域)
- 定义:模块级别定义的变量
- 范围:整个模块(文件)内有效
- 特点:从定义位置开始到文件结束都可见
B - Built-in(内置作用域)
- 定义:Python 内置的名称空间
- 范围:所有模块都可以访问
- 内容:如
print,len,str等内置函数和异常 - (内置作用域)只包含 Python 解释器启动时自动加载的内置函数和异常
- 内置函数:
print(),len(),max(),min(),sum(),abs()等 - 内置异常:
Exception,ValueError,TypeError等 - 内置常量:
True,False,None等、 -
第三方库不在内置作用域内。它们需要通过
import语句导入,导入后会成为模块的全局变量。
- 内置函数:
变量查找顺序
Python 解释器遵循 L → E → G → B 的顺序查找变量:
- 首先在局部作用域查找
- 未找到则在嵌套作用域查找(如果存在)
- 仍未找到则在全局作用域查找
- 最后在内置作用域查找
- 如果都找不到,抛出
NameError
这种查找机制确保了"最近优先"原则,即使用最内层定义的变量。
实际代码示例
示例1:基础 LEGB 演示
# Built-in 作用域(内置函数)
x = 10 # Global 作用域
def outer_function():
x = 20 # Enclosing 作用域
def inner_function():
x = 30 # Local 作用域
print(f"Local x: {x}") # 输出 30
print(f"Enclosing x: {outer_function.__code__.co_freevars}") # 显示闭包变量
inner_function()
print(f"Enclosing x: {x}") # 输出 20
outer_function()
print(f"Global x: {x}") # 输出 10
示例2:变量遮蔽问题
x = 100 # Global
def confusing_function():
# 这里试图修改全局变量,但实际创建了局部变量
x = x + 1 # UnboundLocalError: local variable 'x' referenced before assignment
print(x)
# 正确的修改方式
def correct_function():
global x # 声明使用全局变量
x = x + 1
print(x)
correct_function() # 输出 101
print(x) # 输出 101
示例3:嵌套函数与 nonlocal
def counter():
count = 0 # Enclosing 作用域
def increment():
nonlocal count # 声明使用外层函数的变量
count += 1
return count
def decrement():
nonlocal count
count -= 1
return count
return increment, decrement
inc, dec = counter()
print(inc()) # 输出 1
print(inc()) # 输出 2
print(dec()) # 输出 1
Enclosing vs Global 的核心区别
| 特性 | Global | Enclosing |
|---|---|---|
| 定义位置 | 函数外部 | 外层函数内部 |
| 可见范围 | 整个模块 | 嵌套函数链 |
| 访问方式 | 直接访问 | 通过闭包 |
| 修改关键字 | global | nonlocal |
| 典型应用 | 配置、常量 | 工厂、装饰器 |
| 持久性 | 模块加载到结束 | 外层函数调用期间 |
常见问题与解决方法
问题1:变量遮蔽(Shadowing)
问题:内层作用域的变量遮蔽外层作用域的同名变量
# 问题示例
value = 100
def process():
value = 200 # 遮蔽了全局变量
print(f"内部: {value}")
process()
print(f"外部: {value}") # 仍然是 100
解决方法:
- 使用不同的变量名避免冲突
- 如需修改外层变量,使用
global或nonlocal关键字
问题2:意外的局部变量创建
问题:在函数内对变量赋值会创建局部变量,即使在全局作用域存在同名变量
# 问题示例
name = "Alice"
def greet():
print(name) # 错误!因为后面有赋值操作
name = "Bob"
# 解决方法1:使用 global
def greet_correct():
global name
print(name)
name = "Bob"
# 解决方法2:避免同名
def greet_better():
current_name = name # 读取全局变量
print(current_name)
local_name = "Bob" # 使用局部变量
问题3:global 和 nonlocal 的误用
问题:不清楚何时使用哪个关键字
使用指南:
global:用于修改模块级别的变量nonlocal:用于修改嵌套函数外层的变量
# global 使用场景
config = {"debug": False}
def enable_debug():
global config
config["debug"] = True
# nonlocal 使用场景
def make_multiplier(factor):
def multiply(number):
nonlocal factor # 修改外层函数的变量
factor *= 2
return number * factor
return multiply
multiplier = make_multiplier(5)
print(multiplier(10)) # 输出 20 (factor变成了10)
print(multiplier(10)) # 输出 30 (factor变成了20)
问题4:闭包中的变量绑定
问题:循环中创建的闭包可能绑定错误的变量值
# 问题示例
functions = []
for i in range(3):
def func():
return i # 所有函数都返回最后的 i 值
functions.append(func)
print([f() for f in functions]) # [2, 2, 2]
# 解决方法
functions = []
for i in range(3):
def func(x=i): # 使用默认参数绑定当前值
return x
functions.append(func)
print([f() for f in functions]) # [0, 1, 2]
最佳实践建议
- 命名规范:使用有意义的变量名,避免不同作用域使用相同名称
- 最小化作用域:将变量定义在尽可能小的作用域内
- 显式声明:需要修改外层变量时,显式使用
global或nonlocal - 避免过度嵌套:深层嵌套会使作用域关系复杂化
- 使用类替代:复杂的嵌套函数逻辑可以考虑用类来组织
LEGB 规则是 Python 设计优雅性的体现,理解它有助于编写更清晰、更可预测的代码。在实际开发中,遵循清晰的作用域原则可以避免许多常见的 bug 和困惑。

975

被折叠的 条评论
为什么被折叠?



