Python 作用域:LEGB 规则详解与实战案例
在 Python 中,变量的作用域(Scope) 决定了我们可以在程序的哪个部分访问和操作哪个变量。不理解作用域可能会导致令人困惑的 UnboundLocalError 或意外的行为。Python 使用一种称为 LEGB 的规则来解析变量名。这篇博文将深入探讨 LEGB 规则,并通过大量案例帮助你彻底掌握它。
什么是 LEGB?
LEGB 是一个缩写词,代表了 Python 解释器在查找变量定义时所遵循的四个层次顺序:
-
L - Local (局部作用域)
-
E - Enclosing (闭包函数外的函数作用域)
-
G - Global (全局作用域)
-
B - Built-in (内建作用域)
解释器会按照 L -> E -> G -> B 的顺序由内向外地查找变量。如果在某个层次找到了变量,就停止搜索;如果直到最外层都没找到,则会抛出 NameError 异常。
下面我们来逐一分解这四个作用域。
1. Local (局部作用域 | 当前函数内部)
局部作用域指的是在函数内部定义的变量。这些变量只能在定义它们的函数内部被访问。当函数执行结束后,其内部的局部变量通常会被销毁。
案例 1:简单的局部变量
python
def greet():
message = "Hello, World!" # message 是局部变量
print(message)
greet() # 输出: Hello, World!
print(message) # 报错: NameError: name 'message' is not defined
在上面的例子中,message 是在 greet 函数内部定义的,因此它是局部变量,只能在函数内部使用。
2. Enclosing (闭包函数外的函数作用域 | 嵌套函数)
当一个函数(嵌套函数)被定义在另一个函数(外层函数)的内部时,就产生了闭包作用域。嵌套函数可以访问其外层函数的变量。
案例 2:理解 Enclosing 作用域
python
def outer_func():
enclosing_var = "I'm from outer function" # 外层函数变量 (Enclosing)
def inner_func():
# inner_func 可以访问 outer_func 的变量
print("Inner function says:", enclosing_var)
inner_func()
outer_func() # 输出: Inner function says: I'm from outer function
案例 3:经典的闭包(Closure)
python
def make_multiplier(x): # x 对于 inner_func 是 Enclosing 变量
def inner_func(y):
return x * y # 内部函数记住了外部函数的变量 x
return inner_func
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5)) # 输出: 10 (2 * 5)
print(triple(5)) # 输出: 15 (3 * 5)
inner_func 可以访问其 Enclosing 作用域(即 make_multiplier 函数)中的变量 x,即使 make_multiplier 已经执行完毕。这就是闭包的强大之处。
3. Global (全局作用域 | 模块级别)
在 Python 模块(一个 .py 文件)的顶层定义的变量是全局变量。它们在整个模块中都是可访问的,包括模块内的所有函数。
案例 4:访问全局变量
python
global_var = "I'm a global variable" # 全局变量
def my_function():
# 函数内部可以读取全局变量
print("Inside function:", global_var)
my_function() # 输出: Inside function: I'm a global variable
print("Outside function:", global_var) # 输出: Outside function: I'm a global variable
关键点:global 关键字
在函数内部,如果你想要修改全局变量(而不是创建一个新的同名局部变量),你必须使用 global 关键字进行声明。
案例 5:修改全局变量(需要 global)
python
count = 0
def increment():
global count # 声明我们要修改的是全局变量 count
count += 1
print("Count inside function:", count)
increment() # 输出: Count inside function: 1
increment() # 输出: Count inside function: 2
print("Count outside function:", count) # 输出: Count outside function: 2
案例 6:不使用 global 的后果
python
count = 0
def increment_wrong():
# 没有使用 global 关键字,Python 认为 count 是一个新的局部变量
count = count + 1 # 在执行赋值操作前,局部变量 count 还未被定义,因此这里会报错
print(count)
increment_wrong() # 报错: UnboundLocalError: local variable 'count' referenced before assignment
4. Built-in (内建作用域 | Python 内置模块)
这是最外层的作用域,包含了 Python 的内置函数和异常,如 print(), len(), list, ValueError 等。我们可以在任何地方直接使用它们,而无需导入任何模块。
案例 7:使用内建函数
python
# len 是内建作用域中的函数 my_list = [1, 2, 3] length = len(my_list) # 这里查找 len,最终在 Built-in 作用域找到 print(length) # 输出: 3
注意:虽然很少见,但你也可以覆盖内建函数(强烈不推荐!)。
python
# 警告:不要在实际项目中这样做!
def len(x): # 这里我们用一个全局的 len 覆盖了内建的 len
return 999
my_list = [1, 2, 3]
print(len(my_list)) # 输出: 999 (现在使用的是我们自定义的 len)
# 要恢复使用内建函数,可以删除这个自定义版本
del len
print(len(my_list)) # 输出: 3 (现在使用的是内建的 len)
综合案例与 LEGB 查找顺序
让我们看一个综合案例,清晰地展示 LEGB 的查找顺序。
案例 8:完整的 LEGB 演示
python
x = "global x" # Global
def outer():
x = "enclosing x" # Enclosing (对于 inner 来说)
def inner():
x = "local x" # Local
print(x) # 首先查找 Local,找到了 "local x"
inner()
print(x) # 在 outer 的作用域,找到了自己的 x "enclosing x"
outer()
print(x) # 在全局作用域,找到了 "global x"
# 输出:
# local x
# enclosing x
# global x
案例 9:当内层没有定义时,向外查找
python
x = "global x"
def outer():
x = "enclosing x"
def inner():
# inner 内部没有定义 x,所以向上查找 Enclosing 作用域
print(x) # 输出: enclosing x
inner()
outer()
案例 10:nonlocal 关键字
与 global 类似,nonlocal 关键字用于在嵌套函数中修改外层(非全局)函数的变量。
python
def outer():
count = 0
print("Outer count before:", count) # 输出: 0
def inner():
nonlocal count # 声明我们要修改的是外层(Enclosing)的 count,而不是全局的
count += 1
print("Inner count:", count)
inner()
print("Outer count after:", count) # 输出: 1
outer()
# 如果没有 nonlocal count 这一行,代码会报 UnboundLocalError
总结与要点
| 作用域 | 定义位置 | 关键字 |
|---|---|---|
| Local (L) | 函数内部 | - |
| Enclosing (E) | 嵌套函数的外层函数 | nonlocal |
| Global (G) | 模块顶层 | global |
| Built-in (B) | Python 内置模块 | - |
-
查找顺序: LEGB (Local -> Enclosing -> Global -> Built-in)。
-
赋值操作即定义:在函数内部对变量进行赋值(如
x = 10),Python 会默认将其视为创建新的局部变量,除非使用了global或nonlocal关键字明确声明。 -
只读不写:如果只是引用(读取)变量,Python 可以顺利地按照 LEGB 规则向外查找。但若要修改,则需要关键字声明。
-
避免命名冲突:良好的编程习惯是避免使用与全局变量或内置函数同名的局部变量,这可以提高代码的可读性并减少错误。
理解 LEGB 规则是成为一名合格 Python 程序员的关键一步,它能帮助你更好地组织代码、理解闭包和装饰器,并有效地调试变量作用域相关的问题。
更多推荐


所有评论(0)