Python函数默认参数为可变对象(如[]、{})时会复用同一对象,因其在定义时创建并存于__defaults__中;应改用None并在函数内初始化以避免跨调用共享。

为什么def func(lst=[])会复用同一个列表
因为Python在函数定义时就求值并存储默认参数,[]这个空列表对象只创建一次,之后所有未传参的调用都引用它在内存中的同一地址。这不是“bug”,而是语言设计决定的——函数对象的__defaults__属性直接持有该列表的引用。
你可以用id()验证:
def check(lst=[]):
print(id(lst))
check() # 输出一个地址
check() # 输出完全相同的地址
两次调用打印出的id一致,说明是同一个对象。
lst=[]和lst=None的行为差异
关键区别在于初始化时机和对象生命周期:
-
lst=[]:列表在def执行时创建,绑定到函数的__defaults__元组里,永久存在 -
lst=None:None是不可变单例,安全;每次进入函数体后,if lst is None: lst = []才新建列表,作用域限于本次调用
后者让可变对象的创建从“函数定义期”推迟到“每次调用期”,彻底切断跨调用共享。
哪些类型会踩这个坑
所有可变内置类型都适用同一规则:
-
list([])→ 改用None+if param is None: param = [] -
dict({})→ 改用None+if param is None: param = {} -
set(set())→ 改用None+if param is None: param = set() -
bytearray、自定义可变类实例同理
而int、str、tuple、FrozenSet等不可变类型不会出现此问题,可放心作默认值。
容易被忽略的隐性陷阱
真正难排查的是那些不直接修改默认参数、但间接导致状态残留的场景:
- 把默认列表传给另一个函数,而那个函数内部做了
.append()或.update() - 在装饰器中缓存了带默认可变参数的函数结果
- 多线程环境下多个线程同时调用该函数,共享列表引发竞态写入
- 单元测试中连续调用该函数,前一个测试污染了后一个测试的输入
这些情况不会立刻报错,但数据会“悄悄变胖”,直到某次输出异常才暴露——所以只要看到函数签名里有=[]或={},就该本能地改成=None。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











