Python使用LEGB规则来查找变量,即按照局部(Local)→ 嵌套(Enclosing)→ 全局(Global)→ 内置(Built-in)的顺序进行搜索。
一. 局部作用域(Local)
局部作用域是指在函数内部定义的变量所在的作用域。这些变量只能在函数内部访问,函数执行结束后会被销毁。
def my_func(): x = 10 # x 是局部变量 print(x)my_func() # 输出: 10# print(x) # 报错: NameError: name 'x' is not defined
二. 嵌套作用域(Enclosing)
嵌套作用域出现在函数嵌套(函数内部定义另一个函数)的情况下。外层函数的变量对内层函数来说是“嵌套作用域”中的变量,内层函数可以访问这些变量,但不能修改(除非使用 nonlocal 关键字)。
def outer(): y = 20 # 外层变量 def inner(): print(y) # 内层函数可以访问外层变量 inner()outer() # 输出: 20
三. 全局作用域(Global)
全局作用域是指在模块(文件)顶层定义的变量所在的作用域(不在任何函数内)。全局变量可以在整个模块中访问,但在函数内部修改全局变量需要使用 global 关键字。
z = 30 # 全局变量def show(): print(z) # 可以访问全局变量def modify(): global z z = 40 # 修改全局变量show() # 输出: 30modify()show() # 输出: 40
四. 内置作用域(Built-in)
内置作用域包含了Python预定义的名称,如 print、len、range 等。这些名称在程序的任何地方都可以直接使用。
print(len([1, 2, 3])) # 输出: 3,len 来自内置作用域
五. LEGB规则详解
当在函数内部引用一个变量时,Python解释器会按照以下顺序查找:
Local:首先在当前函数(或lambda)的局部作用域中查找变量。
Enclosing:如果没找到,则向外层嵌套函数的作用域查找(如果有嵌套的话)。
Global:如果还没找到,则到全局作用域(当前模块)查找。
Built-in:最后在内置作用域中查找。
如果所有作用域中都找不到该变量,就会抛出 NameError。
🌲示例:LEGB查找顺序
# 内置作用域中的名称print("Hello") # print 是内置函数x = "global x" # 全局变量def outer(): x = "outer x" # 外层嵌套变量 def inner(): x = "inner x" # 局部变量 print(x) # 输出: inner x (局部) inner()outer()def outer2(): x = "outer x" def inner2(): print(x) # 未定义局部 x,向外层找 inner2()outer2() # 输出: outer xdef outer3(): def inner3(): print(x) # 未定义局部,无嵌套,找全局 inner3()outer3() # 输出: global xdef error_func(): print(unknown_var) # 在所有作用域中都找不到# error_func() # 报错: NameError
六. global 和 nonlocal 语句
⏳global 关键字
用于在函数内部声明一个变量是全局变量,从而可以修改它(而不是创建一个新的局部变量)。
counter = 0def increment(): global counter counter += 1increment()increment()print(counter) # 输出: 2
📚nonlocal 关键字
用于在嵌套函数中声明一个变量来自外层函数(非全局),从而可以修改它。
def outer(): count = 0 def inner(): nonlocal count count += 1 print(count) inner() inner()outer() # 输出: 1\n2
⏳重要提示:
global 仅在函数内使用,不能用于修改内置作用域。nonlocal必须在嵌套函数中使用,且不能作用于全局变量。
七. 常见陷阱与注意事项
1、变量赋值即定义:在函数内部对变量进行赋值(即使是 += 这类操作),默认会将其视为局部变量,除非使用 global 或 nonlocal 声明。
a = 1def test(): print(a) # 这里会引发 UnboundLocalError,因为后面有 a = 2 a = 2test() # 报错
这是因为Python在编译函数时,如果发现函数内部有对 a 的赋值,就会将 a 标记为局部变量,导致前面的 print(a) 试图访问一个未定义的局部变量。
2、可变对象的修改:如果不重新赋值,只是修改可变对象的内容(如列表的 append),不需要 global 或 nonlocal。
lst = [1, 2]def add_item(): lst.append(3) # 修改列表内容,不是重新赋值add_item()print(lst) # 输出: [1, 2, 3]
3、内置名称的覆盖:避免使用与内置函数相同的变量名,否则会覆盖内置作用域中的名称,导致后续无法使用该内置功能。
len = 10 # 覆盖了内置函数 lenprint(len("abc")) # 报错: 'int' object is not callable
4、未使用global
x = 10def bad_example(): x = 20 # 未声明global → 创建局部变量x,不影响全局xbad_example()print(x) # 输出: 10 (正确!但非预期结果)
验证:
5、nonlocal 误用(变量未定义)
def outer(): def inner(): nonlocal x # 错误!x未在Enclosing作用域中定义 x = 30 inner()outer() # 运行时抛出: NameError: name 'x' is not defined
验证:
八. 作用域与命名空间
作用域与命名空间密切相关。命名空间是名称到对象的映射,而作用域是命名空间在代码中的可见范围。不同的作用域对应不同的命名空间:
可以使用内置函数 locals()、globals() 查看当前局部和全局命名空间的内容。