作用域与闭包
问题
Python 的 LEGB 规则是什么?global 和 nonlocal 怎么用?闭包有哪些陷阱?
答案
LEGB 规则
Python 按 L → E → G → B 顺序查找变量:
| 层级 | 全称 | 说明 |
|---|---|---|
| L | Local | 函数内部局部作用域 |
| E | Enclosing | 外层嵌套函数的作用域 |
| G | Global | 模块级全局作用域 |
| B | Built-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',)