连续三次调用append_to(item)不传target,验证默认列表在调用之间是否被共享,用__defaults__确认背后的对象关系。
默认参数是个列表,直觉上以为每次调用不传target就该是个新的空列表。
代码def append_to(item, target=[]): target.append(item) return target r1 = append_to(1) r2 = append_to(2) r3 = append_to(3)
三次调用,一次比一次"多"。
结果第1次调用append_to(1): [1] 第2次调用append_to(2)(没传target): [1, 2] ← 包含了上次的1! 第3次调用append_to(3): [1, 2, 3] r1 is r2: True r2 is r3: True
每一次返回的都是同一个列表对象——不是"看起来一样",是的同一个对象,is 判断全部为True。
用__defaults__直接看函数对象自己保存的默认值。
结果默认参数对象的id全程没变: 4342877056 append_to.__defaults__: ([1, 2, 3],)
原因是:def 语句在被执行(也就是函数被定义)的那一刻,[] 这个默认值表达式只被求值了一次,结果被存进函数对象自己的 __defaults__ 属性里,之后每次调用只要没传 target,用的都是存在 __defaults__ 里的那同一个列表——它不会随着每次调用重新创建,函数对象本身在整个程序运行期间只有一份 __defaults__。
__defaults__ 里变成了 ([1, 2, 3],)——每次 target.append(item) 修改的都是这同一个列表,痕迹会一直累积下去,只要这个函数对象还活着。
用None做哨兵,把"创建新列表"这件事挪到函数体内部。
代码与结果def append_to_fixed(item, target=None): if target is None: target = [] target.append(item) return target f1 = append_to_fixed(1) f2 = append_to_fixed(2) 结果: f1=[1], f2=[2], f1 is f2 为False
修复的关键:默认值改成不可变的 None(True单例,不存在"被共享后累积历史数据"的问题),真正的空列表 [] 挪到函数体内部,每次调用函数体都会重新执行一遍 target = [],创建一个全新的对象——这才是符合直觉的"每次调用都是独立的一份"。
每一步都是本文上面用Python 3.14跑出来的原始结果。
__defaults__ / id():验证默认参数对象只创建一次