Python局部作用域是函数内部独有的变量命名空间,每当调用一个函数,解释器就会新建一个局部作用域,函数体里直接赋值的变量都存于其中,函数退出后该空间通常被销毁。这种隔离机制让不同函数可以使用同名变量而不互相干扰,也是理解参数传递、闭包和装饰器的基础。

一、局部作用域的基本表现
当我们在函数内部写一个赋值语句,例如 x = 10,这个 x 就是局部变量。它在函数外并不存在,如果试图在函数外打印,就会抛出 NameError。下面的例子展示了局部变量和外部变量的区别。
很多初学者会误以为函数里能直接改外面的变量,其实只要在函数内有赋值动作,该名字就被解释为局部。即便外层有同名的全局变量,函数也会优先使用自己的局部版本,从而导致常见的 UnboundLocalError。
def show_local():
x = 5 # x是局部变量
print("函数内 x =", x)
show_local()
# print(x) # 取消注释会报错:NameError: name 'x' is not defined
二、LEGB查找规则
Python在解析一个变量名时,按照 LEGB 顺序寻找:Local(局部)、Enclosing(嵌套外层)、Global(全局)、Built-in(内置)。这意味着在函数里读取变量时,如果局部没有,就往外层函数找,再找不到才看全局和内置命名空间。
这种层级查找只针对读取,不针对写入。写入永远落在当前作用域,除非显式用 global 或 nonlocal 声明。借助该规则,内部函数可以轻松复用外部函数的状态,这也是闭包的实现根基。
x = "global"
def outer():
x = "enclosing"
def inner():
print(x) # 按LEGB找到enclosing层的x
inner()
outer() # 输出 enclosing
三、global与nonlocal声明
如果确实要在函数内修改全局变量,必须用 global 关键字声明,否则赋值会新建局部变量。类似地,在嵌套函数中修改外层(非全局)变量,要用 nonlocal 声明,告诉解释器这不是本地局部变量。
滥用 global 会让代码难以追踪,一般推荐通过参数和返回值来传递状态。nonlocal 则常见于闭包计数器或装饰器,用来保存跨调用的中间数据。
count = 0
def increment():
global count
count += 1
print(count)
increment()
increment()
四、局部作用域与参数传递
函数参数在进入函数时,就作为局部变量存在。对于不可变对象,函数内重新赋值只是让局部名指向新对象,不影响调用方的原值;对于可变对象,局部名和原引用指向同一对象,修改其内容会反映到外部。
明白参数也是局部变量,有助于区分“改引用”和“改内容”。下面代码说明,对列表 append 会影响外部,但令参数等于新列表则不会。
def change(lst):
lst.append(4) # 改内容,外部可见
lst = [9, 9] # 改局部引用,外部无影响
data = [1, 2, 3]
change(data)
print(data) # 输出 [1, 2, 3, 4]
五、常见误区与小结
一个典型误区是认为局部作用域在定义时就固定。其实它在函数每次调用时动态创建,因此递归或并发调用各自拥有独立局部空间,不会串数据。另一个误区是在嵌套函数里直接赋值外层变量,忘记 nonlocal 而得到报错。
整体来看,局部作用域是 Python 命名隔离的核心。掌握 LEGB、global、nonlocal 以及参数局部性,就能准确预判变量读写行为,减少作用域相关的 bug,写出结构清晰的模块化程序。