Python中list或dict作默认参数会出问题,因为默认参数在函数定义时只求值一次,多次调用共享同一可变对象,导致数据累积;正确做法是用None作占位符,在函数体内判断并初始化新对象。

为什么 list 或 dict 作默认参数会出问题
Python 函数(包括 __init__)的默认参数在定义时只求值一次,不是每次调用时新建。如果默认值是可变对象(如 []、{}、set()),多次调用会共享同一个对象,导致“上一次调用的修改污染下一次”。常见现象是:类实例间意外共享数据,比如多个对象的 self.items 指向同一列表。
正确写法:用 None 作占位符再初始化
这是最通用、最安全的做法。把默认参数设为 None,在方法体内判断并创建新对象:
class Box:
def __init__(self, items=None):
self.items = items if items is not None else []
注意必须用 is not None 判断,不能用 if not items —— 否则空列表、空字典等“falsy”值会被误判为未传参而覆盖掉用户显式传入的空容器。
哪些场景容易踩坑
- 在
__init__中直接写def __init__(self, tags=[]):—— 多个实例共用一个tags列表 - 用
copy.deepcopy做防御性复制但没意识到源头已被污染 —— 深拷贝的是那个早已被多次修改的共享对象 - 在类方法中用可变默认参数做缓存(比如
def get_data(cache={}))—— 缓存跨调用累积,且无法控制生命周期 - 继承父类时忽略父类
__init__的默认参数设计,子类直接复用导致行为不一致
其他可行但需谨慎的方式
极少数情况下可用工厂函数或 lambda,但可读性和调试性差,不推荐:
快速生成专业的 Python 脚本和应用代码。一键创建完整项目结构,支持CLI、API、爬虫、Bot、Django等多种项目类型,包含完整的项目结构、配置文件、依赖管理、测试、README和文档。
立即学习“Python免费学习笔记(深入)”;
def __init__(self, items=lambda: []):
self.items = items()
更隐蔽的问题是:如果用户传了 items=[],你仍会执行 items(),结果还是新建了一个空列表,违背了“显式传参应被尊重”的原则。所以 None 占位仍是唯一可靠路径。
真正容易被忽略的,是那些看似无害的“空值”传参场景 —— 比如测试时传 [] 或 {},结果发现它被当成未传参处理了。这时候得回头检查判断逻辑是不是用了 if not xxx。

















