defaultdict初始化必须传可调用对象而非值,如defaultdict(list)正确,defaultdict([])报错;嵌套需递归定义如defaultdict(lambda: defaultdict(int));其自动插入键可能掩盖错误,只读场景优先用.get();深拷贝丢失默认行为,pickle要求工厂函数为可导入的顶层函数。

defaultdict 初始化时必须传入可调用对象,不能直接传值
很多人写 defaultdict(0) 或 defaultdict([]),结果报 TypeError: first argument must be callable。这是因为 defaultdict 的构造参数是「默认工厂函数」,不是默认值本身。
正确做法是传入函数名或 lambda:
from collections import defaultdict d = defaultdict(list) # ✅ list 是类,可调用,返回空列表 d = defaultdict(int) # ✅ int() 返回 0 d = defaultdict(lambda: 'N/A') # ✅ lambda 返回固定字符串
常见错误写法:
-
defaultdict([])→ 报错,空列表不可调用 -
defaultdict({})→ 报错,字典字面量不可调用 -
defaultdict('')→ 报错,字符串不可调用
嵌套 defaultdict 容易漏掉内层工厂,导致 AttributeError
想实现类似 d['a']['b']['c'] = 1 的多级结构,但只写了 defaultdict(dict),访问 d['a']['b']['c'] 仍会报 KeyError —— 因为内层 dict 不是 defaultdict,不会自动创建键。
立即学习“Python免费学习笔记(深入)”;
解决方式有两种:
- 用
lambda递归构造:defaultdict(lambda: defaultdict(int)) - 封装成函数避免重复:
def nested_dict(): return defaultdict(nested_dict),然后d = defaultdict(nested_dict)
注意:递归定义的 nested_dict 函数必须在调用前定义,且不能写成 lambda: defaultdict(lambda: ...)(嵌套 lambda 可读性差、调试困难)。
defaultdict 会静默创建键,可能掩盖逻辑错误
defaultdict 在首次访问缺失键时自动调用工厂函数并插入该键,这和普通 dict.get() 的“只读不写”行为完全不同。如果误把 d[key] 当作安全读取,实际已污染原字典。
典型场景对比:
d = defaultdict(int)
print(d['missing']) # 输出 0,且 d 现在含键 'missing'
# 而普通 dict:
d2 = {}
print(d2.get('missing', 0)) # 输出 0,但 d2 仍为空所以当只需要「读取默认值」而不想修改字典时,优先用 .get();只有明确需要「自动初始化+后续赋值」时,才用 defaultdict。
性能差异小,但 deepcopy 时要注意工厂函数丢失
创建大量 defaultdict 实例时,工厂函数本身开销几乎可忽略(int、list 都是内置可调用对象)。真正容易出问题的是序列化或深拷贝:
-
copy.deepcopy(d)得到的是普通dict,不再有默认行为 -
pickle可以保存工厂函数,但要求函数必须可导入(不能是临时 lambda 或闭包)
如果需要持久化或跨进程传递,默认工厂推荐用顶层定义的函数,比如:
def default_zero(): return 0 d = defaultdict(default_zero)
而不是 defaultdict(lambda: 0) —— 后者在 unpickle 时会失败。


















