闭包延迟绑定陷阱与惰性求值问题
请解释Python闭包中延迟绑定(late binding)导致的常见陷阱,尤其是在循环中创建lambda或嵌套函数时。给出三种解决方案(默认参数绑定、functools.partial、闭包工厂函数)。
回答
小字辈
陷阱示例
def create_actions():
actions = []
for i in range(4):
actions.append(lambda: i) # 延迟绑定
return actions
for action in create_actions():
print(action(), end=' ') # 3 3 3 3 <- 不是 0 1 2 3
原因:闭包捕获的是自由变量i的引用,而非创建时的值。循环结束后i=3,所有lambda访问的都是最终的i。
解决方案
方案1:默认参数(立即绑定)
def create_actions():
actions = []
for i in range(4):
actions.append(lambda i=i: i) # 默认参数在定义时求值
return actions
# 输出:0 1 2 3
方案2:functools.partial
from functools import partial
def create_actions():
actions = []
for i in range(4):
actions.append(partial(lambda x: x, i))
return actions
# 输出:0 1 2 3
方案3:闭包工厂函数
def make_action(value):
return lambda: value
def create_actions():
return [make_action(i) for i in range(4)]
# 输出:0 1 2 3
面试回答要点
区分「定义时求值」(默认参数)和「调用时求值」(闭包变量),两种看似相反但实际一致的机制——都取决于Python的作用域规则和函数创建时机。