闭包延迟绑定陷阱
为什么循环中创建的 lambda 都返回同一个值
- 别搞错了:闭包捕获的是变量的引用,不是变量的值。
- 识别循环中 lambda 的延迟绑定问题
- 掌握默认参数立即绑定的解法
- 理解 Python 作用域查找时机
闭包延迟绑定陷阱
循环里创建的函数,为啥都返回最后一个值?
陷阱重现
闭包捕获的是变量本身(引用),不是创建时的值。Python 调用闭包时才查变量的值,不是定义时查。这叫「延迟绑定」(late binding)。
解法一:默认参数立即绑定
解法二:工厂函数
实际场景:按钮回调
实际场景:GUI 按钮回调(Tkinter/PyQt)错误写法for i in range(5): btn = Button(text=f"按钮{i}") btn.on_click(lambda: print(i)) # 所有按钮都打印4! 正确写法for i in range(5): btn = Button(text=f"按钮{i}") btn.on_click(lambda i=i: print(i)) # 每个按钮打印自己的i 同样的问题也出现在:循环中创建线程/进程循环中注册信号处理循环中定义回调函数GUI回调、信号处理、异步任务中常见此坑
异步编程里这个坑特别常见:循环里创建 asyncio.Task 或者 add_done_callback 时,回调里用了循环变量,所有回调拿到的都是最终值。解法就俩:要么用默认参数绑定,要么用工厂函数。就记死:闭包抓的是变量的引用,不是值。要拿值的话,定义的时候就拷贝一份。
funcs = [lambda: x for x in range(3)]
print(funcs[0](), funcs[1](), funcs[2]())资深工程师加餐
底层原理 · 大厂视角 · 工程经验,点卡片展开
list 底层是一段连续的指针数组,容量不够时整体扩容(CPython 约按 1.125 倍)。因此按下标取元素是 O(1),末尾 append 均摊 O(1),但在头部 insert(0,…) 或 pop(0) 要搬动全部元素、是 O(n)。dict/set 基于哈希表,平均查找 O(1) 但更耗内存。选对数据结构,往往比抠语法更影响性能。
挑战任务
修复回调函数
修复下面的代码,让每个按钮回调输出对应的数字。
课后作业
安全的乘法表
用列表推导创建一个函数列表,每个函数返回对应的乘法结果。