可变默认参数陷阱
Python 最著名的坑:def f(x=[]) 到底发生了什么
- 理解默认参数在函数定义时求值
- 识别可变默认参数陷阱
- 掌握正确的 None 惯用法
- 理解 None 作为哨兵值的用法
可变默认参数陷阱
这是 Python 面试中最常考的题,也是新手最容易踩的坑。真正理解它,你就避开了一个绝大多数新手都会反复栽跟头的陷阱。
陷阱重现
Python 的默认参数值在函数定义时(def 语句执行时)求值一次,之后每次调用都复用同一个对象。如果默认值是可变对象(list/dict/set),多次调用之间会共享状态。
正确写法
这个设计不是 bug,是 Python 的有意为之。默认参数在定义时求值有一个好处:可以实现缓存/memoization。但绝大多数情况下你不想要这个行为。记住这个惯用法:def f(x=None): if x is None: x = []。这是 Python 社区的标准写法,所有 linter 都会警告 mutable default argument。
哪些类型安全?
默认参数的底层实现
函数对象有一个 __defaults__ 属性,存储默认参数的元组:
这就是默认参数在定义时求值的原因——它们就挂在函数对象上,和函数同生命周期。
资深工程师加餐
底层原理 · 大厂视角 · 工程经验,点卡片展开
内层函数(或 lambda)捕获的是外层变量本身,而不是定义那一刻的值;循环里定义的多个函数,循环结束后调用往往都拿到最后的值。解决办法是用默认参数 f(x=x) 立即绑定,或用工厂函数包一层。配合 LEGB 作用域顺序(局部→外层→全局→内建)理解,90% 的作用域谜题都能解开。
挑战任务
修复默认参数
修复下面的函数,每次调用不传 cache 时,就用独立的字典。
课后作业
安全的累加器
写一个函数,每次调用返回累加值,不传参数时从0开始。