在Python中定义函数时,如果给参数设置了默认值,这个默认值只在函数定义的时候被计算一次。当我们使用列表、字典这类可变对象作为默认参数时,就容易出现数据在多次调用之间互相干扰的问题。理解这个机制对写出健壮的代码非常重要。

问题现象
下面这段代码看起来只是想在函数被调用且没有传入参数时返回一个空列表,但实际运行结果可能超出预期:
def add_item(item, target=[]):
target.append(item)
return target
print(add_item('a')) # 输出 ['a']
print(add_item('b')) # 输出 ['a', 'b']
print(add_item('c')) # 输出 ['a', 'b', 'c']
第二次调用并没有得到新的空列表,而是在第一次的结果上继续添加了内容。
原因解析
Python在函数定义阶段就会执行默认参数的表达式,并把得到的对象绑定到函数对象的属性上。对于可变对象来说,它只在内存中创建一次,之后每次调用只要没传实参,使用的都是同一个对象。
- 函数定义时:target 指向一个列表对象
- 第一次调用:往该列表加 'a'
- 第二次调用:还是同一个列表,继续加 'b'
默认参数是函数对象的静态属性,不是每次调用时重新生成的局部变量。
正确的写法
常见的做法是把默认参数设为 None,在函数体内再创建新的可变对象:
def add_item(item, target=None):
if target is None:
target = []
target.append(item)
return target
print(add_item('a')) # 输出 ['a']
print(add_item('b')) # 输出 ['b']
使用类型注解的写法
如果使用了类型提示,也可以清晰地表达参数可为空:
from typing import List, Optional
def add_item(item: str, target: Optional[List[str]] = None) -> List[str]:
if target is None:
target = []
target.append(item)
return target
总结
Python默认参数不能使用可变对象,根本原因在于默认值在定义时求值且只生成一次。用 None 替代可变默认值,并在函数内部初始化,可以避免状态共享带来的隐蔽错误。养成这个习惯,能让函数行为更加可预期,也更容易测试和维护。