不能用[]或{}做默认参数,因为可变对象在函数定义时创建一次,所有调用共享同一对象,导致多次调用时状态累积;应改用None占位并在运行时初始化,或在__init__中初始化实例属性。

为什么不能用 [] 或 {} 做默认参数
因为可变对象(如列表、字典)在函数定义时就创建一次,所有后续调用共享同一个对象。一旦被修改,下次调用会看到“脏”状态。
典型错误现象:append() 多次调用后元素不断累积,而不是每次从空容器开始。
- 场景:类方法中用于收集临时数据、缓存中间结果、记录调用历史
- 影响:多实例间意外共享、并发下行为不可预测、单元测试失败
- 示例:
def add_item(self, item, items=[]): # 危险! items.append(item) return items第二次调用会返回[item1, item2],不是预期的[item2]
用 None 作为占位符并内部初始化
这是最常用也最安全的替代方式——把可变对象的创建推迟到运行时,每次调用都新建一份。
关键点是检查是否为 None,再赋值;不要直接写 items=[] 或 items={}。
立即学习“Python免费学习笔记(深入)”;
- 正确写法:
def add_item(self, item, items=None): if items is None: items = [] items.append(item) return items - 类中常见位置:
__init__方法里初始化实例属性(如self.items = []),而非作为方法参数 - 注意:不要用
if not items:判断,空列表也会被误判为 False
使用 types.MappingProxyType 或 tuple 限制意外修改
如果默认值本意是只读配置(比如字段映射表、常量选项),用不可变类型封装更稳妥。
避免开发者无意中 .update() 或 .append() 破坏默认行为。
-
MappingProxyType是字典的只读视图:from types import MappingProxyType DEFAULT_OPTS = MappingProxyType({"timeout": 30, "retries": 3}) - 传参时仍可用
opts=None+opts or DEFAULT_OPTS组合 - 元组适合简单列表场景:
DEFAULT_TAGS = ("draft", "pending"),比[]更明确表达“不可变”意图
在 __init__ 中初始化实例属性,而非依赖参数默认值
类设计中,真正需要“每个实例独有”的可变状态,应该放在 __init__ 里初始化,而不是塞进方法参数。
这样逻辑清晰、职责分明,也规避了所有默认参数陷阱。
- 错误示范:
class Processor: def __init__(self, cache={}): # 危险! self.cache = cache - 正确做法:
class Processor: def __init__(self, cache=None): self.cache = cache or {} # 或直接 self.cache = {} - 如果需支持传入自定义缓存对象(如
LRUCache),保留cache=None并做类型检查即可
真正容易被忽略的是:哪怕你没在参数里写 [],如果在 __init__ 里用了 cache={} 这种字面量,照样共享。只要对象是在定义时创建的,就逃不掉这个坑。


















