Python 函数默认参数在定义时求值,可变对象如 []、{} 被重复使用导致状态残留;正确做法是用 None 作默认值并在函数内初始化。

为什么 def func(x=[]): 会“记住”上次调用的修改
Python 函数的默认参数在函数定义时就完成求值,而不是每次调用时重新创建。这意味着 [] 这个空列表对象只在 def 执行那一刻生成一次,后续所有未传参的调用都共享这个同一对象。
常见错误现象:
def append_to(a, lst=[]): lst.append(a) return lst print(append_to(1)) # [1] print(append_to(2)) # [1, 2] ← 意外!不是 [2]
- 根本原因不是“变量被污染”,而是
lst始终指向同一个list实例 - 该行为对所有可变对象都成立:包括
{}、set()、自定义类实例等 - 不可变对象(如
None、0、"a")没这个问题,因为它们无法被原地修改
正确写法:用 None 作为占位符
标准解法是把默认值设为 None,再在函数体内显式初始化可变对象。
def append_to(a, lst=None): if lst is None: lst = [] lst.append(a) return lst
-
is None比== None更安全,且是 Python 社区惯例 - 如果需要预设初始内容(比如默认带一个元素),直接写
lst = [default_item] - 不要用
if not lst:判断——空列表是 falsy,但用户可能真想传入空列表
哪些场景容易踩坑
这类问题高发于构造器、缓存逻辑、递归辅助参数、配置合并等场景。
- 类方法中默认参数用
dict:导致多个实例共享同一字典,互相覆盖键值 - 装饰器里缓存结果:若用
cache={}当默认参数,所有被装饰函数共用一个缓存字典 - 递归函数 的 accumulator:比如
def flatten(lst, acc=[]),不同调用链会混在一起 - Flask/Django 视图函数:默认参数若含可变对象,在多请求下状态错乱,极难复现
检查现有代码是否受影响
搜索项目中所有形如 =[]、={}、=set() 的函数参数定义,尤其是非 None 默认值。
立即学习“Python 免费学习笔记(深入)”;
- 静态检查 工具(如
pylint)能报dangerous-default-value警告 - 运行时可通过
inspect.signature(func).parameters查看默认值对象 ID 是否变化 - 注意嵌套结构:例如
def f(d={"k": []})同样危险——外层 dict 不可变,但内部 list 是可变的
真正麻烦的不是语法错误,而是它只在特定调用序列下暴露,且表现像“偶发 bug”。一旦默认参数里藏了可变对象,它的生命周期就脱离了你的直觉控制。






























