Python setdefault:为什么默认值也会提前执行?
把“键不存在时插入默认值”理解成“键不存在时才执行默认值表达式”,很容易在 Python 的 setdefault 中踩坑。下面用一次函数调用记录,拆开这两个时机。
1. 先执行参数表达式,再调用 setdefault
calls = []
def make_default():
calls.append("built")
return []
data = {"tasks": ["ready"]}
result = data.setdefault("tasks", make_default())
print(result) # ['ready']
print(calls) # ['built']
print(data) # {'tasks': ['ready']}
tasks 已经存在,所以字典保留原来的列表,并返回它。与此同时,make_default() 已经执行了一次。
执行顺序是:求出 make_default() 的结果,把这个结果作为第二个实参,然后调用 setdefault。字典是否插入值,是方法收到实参之后才作出的判断。如果默认值函数有计数、日志或其他副作用,即使键已存在,这些操作也会发生。
2. 返回值与插入值可以是同一个对象
data = {}
bucket = data.setdefault("tasks", [])
bucket.append("run")
print(data) # {'tasks': ['run']}
print(bucket is data["tasks"]) # True
键缺失时,第二个实参成为字典里的值,返回值也指向它。因此,通过 bucket 修改这个列表,字典中看到的列表会一起变化。
setdefault 依据键是否存在作判断。若已有值是 None、False、0 或空列表,它会返回该值。需要替换这些值时,应单独写清业务条件。
3. 需要按需创建时,显式检查键
下面的函数用于普通 dict 的单线程读写:
def get_or_create(mapping, key, factory):
if key not in mapping:
mapping[key] = factory()
return mapping[key]
calls = []
def make_default():
calls.append("built")
return []
data = {"tasks": []}
existing = get_or_create(data, "tasks", make_default)
created = get_or_create(data, "new", make_default)
print(existing) # []
print(calls) # ['built']
print(created is data["new"]) # True
这里把函数本身传入 factory。已有 tasks 时,函数直接返回原列表;只有 new 缺失时,才调用一次 factory。保留空列表的判断也来自键的存在性,避免把“存在但为空”误判成“缺失”。
对于简单默认值,setdefault 可以表达简洁的插入逻辑;对于有副作用、可能抛异常或需要按需执行的初始化,把创建时机写出来更容易检查。示例没有覆盖自定义映射或并发写入,不能据此推断这些场景的行为。
验证与官方参考
本文三段代码已分别实际运行,并补充验证了已有键、缺失键、可变对象身份、空值以及默认值函数抛异常的情况。结果只说明这些最小示例的行为。
官方来源:Python 官方文档 Built-in Types 的 dict.setdefault,以及 Expressions 的 Evaluation order。可按以下路径核对原文:
docs.python.org/3/builtins/stdtypes.html#dict.setdefaultdocs.python.org/3/reference/expressions.html#evaluation-order
AI 辅助说明:本文由 AI 协助组织文字与编写示例,示例已执行验证,并依据上述 Python 官方文档核对语义。
- 点赞
- 收藏
- 关注作者
评论(0)