Python函数默认参数:为什么列表会记住上一次调用
写一个接收列表参数的函数时,开发者有时会把空列表直接写成默认值,期待每次调用都得到一个新的容器。实际运行后却发现,第二次调用里出现了第一次保存的数据。这种现象不是 Python 自动记录了调用历史,而是多次调用使用了同一个默认对象。
理解这一点,需要区分函数定义和函数调用。默认参数表达式在执行函数定义时求值,而不是每次进入函数时重新执行。如果默认值是一个列表,那么函数定义完成后,这个列表对象就已经存在。调用者省略该参数时,函数会继续使用这个默认列表。
列表可以原地修改。向它追加元素、删除元素或调整顺序,改变的是同一个对象的内容。如果函数内部把一次处理结果追加到默认列表,下次省略参数调用时,就能看到上次留下的元素。字典和集合也有类似风险,因为它们同样允许原地修改。
常见的处理方式是把默认值设为 None,在函数内部判断参数是否为 None;如果是,再创建一个新的空列表。这样,新列表在每次需要时才会生成。判断时应写成“是否为 None”的语义,而不是笼统判断真假,否则调用者明确传入的空列表也可能被替换,改变接口原本的含义。
例如,调用者传入一个空列表,希望函数将结果直接追加进去,并在调用后检查该列表的内容。如果函数只因为列表为空就重新创建容器,调用者持有的原列表便不会得到更新。None 表示“没有提供容器”,空列表表示“提供了一个目前没有元素的容器”,二者应当分开处理。
还要注意,使用 None 只能解决默认对象共享的问题,不能自动保证函数不修改调用者的数据。如果调用者主动传入列表,函数继续向其中追加内容,外部列表仍然会变化。接口需要明确说明:函数是在原容器上累加,还是先复制一份再处理。复制外层列表也只是浅复制,里面的嵌套对象仍可能被共同引用。
默认参数并不只有可变容器值得留意。假如把获取当前时间的函数调用写在默认值里,得到的也会是定义时的时间,而不是每次调用时的时间。需要实时值时,应在函数体内生成。理解求值时机,比记住“不要用空列表当默认参数”更有帮助。
有些程序会有意利用默认对象保存状态,但这种写法隐藏了状态的生命周期,测试和并发使用时更难理解。如果确实需要共享缓存,通常应该通过显式的缓存对象、实例属性或其他清晰的结构表达,让调用者知道数据会在多次调用之间保留。
检查此类函数时,可以连续省略参数调用两次,再分别传入空列表和已有内容的列表,观察结果是否符合约定。把“省略参数”“传入空容器”“是否修改原数据”三种情况讲清楚,函数的行为就会更容易预测。
- 点赞
- 收藏
- 关注作者
评论(0)