默认参数的可变对象会“记住”上次修改,因为Python在函数定义时就将默认值存入__defaults__属性并复用同一内存对象;安全做法是用None占位并在函数内初始化。

因为默认参数在函数定义时就完成了求值和绑定,而不是每次调用时新建——可变对象(如 [])只创建一次,后续所有未传参的调用都复用同一个内存对象。
为什么def f(x=[])的列表会“记住”上次修改?
Python 把默认参数值存进函数对象的 __defaults__ 属性里,这个过程发生在 def 语句执行时。你写 x=[],解释器立刻构造一个空列表,并把它塞进 f.__defaults__。之后每次调用 f() 且不传 x,就直接把那个老列表拿出来用。
- 可以用
print(f.__defaults__)看到它一直挂着 - 用
id(x)打印参数地址,连续调用会返回相同数字 - 这不是 bug,是 Python 函数对象模型的自然结果
list、dict、set 都危险,但 str 和 tuple 安全
可变性才是关键:只要对象支持原地修改(.append()、[key] = val、.add()),又作为默认值被共享,就会出问题。
快速生成专业的 Python 脚本和应用代码。一键创建完整项目结构,支持CLI、API、爬虫、Bot、Django等多种项目类型,包含完整的项目结构、配置文件、依赖管理、测试、README和文档。
- 危险类型:
[]、{}、set()、numpy.ndarray、自定义类实例(若其属性可变) - 安全类型:
None、0、""、()、True—— 它们无法被“悄悄改掉” - 注意:
tuple虽不可变,但若含可变元素(如([],)),仍可能引发间接问题
正确写法:用 None 占位,函数内初始化
这是 PEP 8 明确推荐的模式,也是所有主流代码检查工具(如 mypy、pylint)默认识别的安全写法。
立即学习“Python免费学习笔记(深入)”;
- 写成
def f(x=None): if x is None: x = [],每次调用都拿到新列表 - 字典同理:
data=None→if data is None: data = {} - 别用
if not x:判断,空列表、空字典也会进分支,失去意义 - 加上类型注解更清晰:
def f(x: list[str] | None = None)
真正容易被忽略的是:这个机制不只影响函数参数,类变量(如 cache = [])和闭包中引用外部可变对象时,同样会共享同一实例——本质都是“对象被多处持有 + 原地修改”。

















