可变默认参数(如[]、{})在函数定义时创建一次并被所有调用共享,导致原地修改污染后续调用;应改用None检查(if lst is None: lst = []);仅当明确需跨调用共享状态(如缓存、计数器)时才例外使用。

为什么[]和{}不能直接当默认参数用
因为def func(lst=[])里的[]在函数定义时就被创建一次,之后所有没传lst的调用都共享这个列表对象。不是“每次调用新建一个空列表”,而是“所有调用共用同一个列表”。一旦函数内部调用了.append()、.update()这类原地修改操作,上一次留下的数据就会污染下一次。
常见现象:func(1)返回[1],func(2)却返回[1, 2]——这不是 bug,是 Python 的确定性行为。
if lst is None:为什么必须用is None而不是== None或if not lst
用is None是语义准确且安全的唯一方式:
-
== None虽然多数时候能工作,但类可以自定义__eq__,导致意外结果 -
if not lst会把用户显式传入的空列表[]、空字典{}甚至False、0都当成“未提供”,直接覆盖掉合法输入 - 只有
is None能严格区分“参数没传”和“参数传了但值为假”
哪些场景下[]作为默认参数反而“有用”
极少数情况,你确实需要跨调用共享状态,比如缓存、计数器、调试日志收集器。这时可变默认参数不是 bug,而是有意设计:
立即学习“Python免费学习笔记(深入)”;
-
def expensive_calc(x, cache={}):—— 利用cache避免重复计算 -
def counter(n=0, _count=[0]):—— 用_count记录总调用次数(注意加下划线前缀表明非公开)
但必须满足两个条件:一是在函数文档或注释里明确说明这是共享行为;二是确保所有调用方都知情且接受该契约。否则就是埋雷。
检查和修复现有代码的实操建议
静态扫描比肉眼更可靠:
- 用
pylint跑一遍,它会直接报W0102: Dangerous default value [] as argument - 搜索项目中所有
=\[\]、=\{\}、=set\(\)出现在def语句参数列表中的位置 - 对嵌套结构要小心,比如
def f(d={"k": []})——外层dict不可变,但里面的[]仍是可变的,照样危险
真正麻烦的不是语法报错,而是这种问题只在多轮调用、并发请求或特定参数组合下才暴露,且表现像“偶发失败”,调试成本远高于预防成本。


















