因为默认参数在函数定义时只被创建一次,不是每次调用都新建;若用[]或{}作默认值,多次调用会共享同一对象,导致数据污染,如列表持续累积、字典键不断增多。

为什么 list 或 dict 不能直接当默认参数?
因为默认参数在函数定义时只被创建一次,不是每次调用都新建。如果用 [] 或 {} 作默认值,多次调用会共享同一个对象,导致意外的数据污染。
常见错误现象:连续调用类方法后,前一次的修改影响后一次的结果——比如添加元素后列表越变越长,或字典键越来越多。
- 使用场景:类的
__init__、实例方法、类方法中传入可变默认参数 - 根本原因:Python 的默认参数是函数对象的属性(
func.__defaults__),初始化后就固定了 - 性能影响:看似省了初始化开销,实则埋下隐性 bug,调试成本远高于一行
if xxx is None:
标准写法:用 None 代替可变默认值
这是最通用、最易理解的方案,适用于所有 Python 版本。
class Config:
def __init__(self, tags=None):
self.tags = tags if tags is not None else []
<pre class="brush:php;toolbar:false;">def add_tag(self, tag):
self.tags.append(tag)
立即学习“Python免费学习笔记(深入)”;
关键点:
- 永远检查
tags is None,而不是tags == None(避免自定义类重载__eq__导致误判) - 不要写成
if not tags:—— 空列表、空字典、0、False都会进分支,逻辑错乱 - 构造函数里不直接赋值
self.tags = tags or [],因为tags可能是[0]或[False],被误判为 falsy
需要深拷贝时怎么办?
如果传入的是嵌套可变对象(比如含列表的字典),且你希望每个实例完全隔离,就不能只做引用赋值。
典型场景:配置类接收一个默认模板 default_config = {"options": ["a", "b"]},但不同实例需独立修改其 options 列表。
- 用
copy.deepcopy()显式复制:仅在真正需要隔离嵌套结构时才用,有性能开销 - 更轻量的做法是延迟初始化 + 浅拷贝:例如
self.options = options["options"][:] if options else [] - 避免在默认参数位置写
default_config.copy()—— 这仍然只执行一次,不是每次调用都新 copy
类变量 vs 实例变量:别把可变对象放类定义里
这个坑和默认参数本质相同,但发生位置不同:有人会在类体中直接写 cache = [],结果所有实例共享该列表。
错误示例:
class Processor:
cache = [] # ❌ 所有实例共用一个 list
<pre class="brush:php;toolbar:false;">def process(self, item):
self.cache.append(item) # 后续实例调用会看到之前所有 item
立即学习“Python免费学习笔记(深入)”;
正确做法:
- 移到
__init__中初始化:self.cache = [] - 如果真要类级缓存,明确用
@classmethod+cls._cache,并加锁或文档说明共享语义 - 静态工具类中若需默认状态,仍应走
None检查路径,而非依赖类变量
最容易被忽略的是:这两个问题(默认参数、类变量)触发条件不同,但底层机制一致——对象在定义时生成,而非使用时生成。只要涉及可变对象,就得主动控制生命周期。


















