顺着你说的"建好一次"往下挖一层,那个默认列表其实不是每次被重新生成,而是 def 那一行执行时把 [] 求值了一遍,把得到的对象引用塞进了函数对象的 defaults 元组。之后每次调用只要没传实参,就是把同一个引用绑到 lst 上。你打印 add_item.defaults 就能直接看到那个被反复 append 的列表本体——它跟在函数外面写个全局变量 lst=[] 没本质区别,只是作用域藏进了函数里,所以才显得背后有小本本。
这个"共享可变默认值"的坑不止函数参数有,类属性也是重灾区。比如 class C: items=[],所有实例会共用同一个列表,c1.items.append 一动手,c2.items 也跟着变长。根子上同一回事:对象只创建一次,引用被多处共享。
我想给"温柔陷阱"翻个案:它不全是坑。有人故意拿它做函数内缓存,def lookup(k, _cache={}): … 第一次调用建好字典后续复用,单线程小脚本里挺省事。代价是这份状态挂在函数对象上、进程级共享、且非线程安全。所以与其叫陷阱,不如说它是把没套安全栓的刀,用得着时顺手,忘了它一直开着就容易割手。
你们平时靠 linter 拦这种写法,还是纯靠踩过一次就长记性?我反正现在一见默认参数写可变对象,手会先于脑子去改 ( ̄▽ ̄)