跳到主要内容

作用域与闭包

问题

Python 的 LEGB 规则是什么?globalnonlocal 怎么用?闭包有哪些陷阱?

答案

LEGB 规则

Python 按 L → E → G → B 顺序查找变量:

层级全称说明
LLocal函数内部局部作用域
EEnclosing外层嵌套函数的作用域
GGlobal模块级全局作用域
BBuilt-in内置作用域(print, len 等)
x = "global"                # G

def outer():
x = "enclosing" # E

def inner():
x = "local" # L
print(x) # → "local"

inner()

outer()

global 和 nonlocal

count = 0

def increment():
global count # 声明修改全局变量
count += 1

increment()
print(count) # 1

def make_counter():
n = 0
def counter():
nonlocal n # 声明修改外层函数变量
n += 1
return n
return counter

c = make_counter()
print(c()) # 1
print(c()) # 2
UnboundLocalError 陷阱
x = 10
def func():
print(x) # ❌ UnboundLocalError!
x = 20 # 这行让 Python 认为 x 是局部变量

# Python 在编译时就确定 x 是局部变量(因为有赋值),
# 但 print(x) 时局部 x 尚未赋值

闭包

闭包是引用了外层作用域变量的函数,即使外层函数已经返回:

def make_greeting(prefix: str):
def greet(name: str) -> str:
return f"{prefix}, {name}!" # 引用了外层的 prefix
return greet

hello = make_greeting("Hello")
hi = make_greeting("Hi")
print(hello("Alice")) # Hello, Alice!
print(hi("Bob")) # Hi, Bob!

闭包的循环变量陷阱

# ❌ 常见错误
buttons = []
for i in range(5):
buttons.append(lambda: print(i))

buttons[0]() # 4(不是 0!所有 lambda 共享同一个 i)

# ✅ 修复方案 1:默认参数捕获
buttons = []
for i in range(5):
buttons.append(lambda i=i: print(i))

# ✅ 修复方案 2:functools.partial
from functools import partial
buttons = [partial(print, i) for i in range(5)]

常见面试问题

Q1: 闭包的变量是何时绑定的?

答案

闭包在定义时捕获变量的引用(而非值)。变量的值在调用时才查找:

def lazy():
x = 10
def get_x():
return x # 引用 x,不是复制值
x = 20 # 修改 x
return get_x

print(lazy()()) # 20(不是 10)

Q2: 如何查看闭包捕获了哪些变量?

答案

def outer(x):
def inner():
return x
return inner

fn = outer(42)
print(fn.__closure__) # (<cell ...>,)
print(fn.__closure__[0].cell_contents) # 42
print(fn.__code__.co_freevars) # ('x',)

相关链接