setdefault 是一个丑陋的 dict 方法
Source: Dev.to
Introduction
偶尔我会做一些简单的 Python 小测验来保持技能敏锐,并发现新的语言特性。一次测验要求给出下面表达式的返回值
{i: i**2 for i in range(3)}.setdefault(2, 10)我以前从未使用过 setdefault(),于是从选项 2, 10, 4, 1 中猜测。正确答案是 4,但我不太清楚原因。
How setdefault() Works
Python 文档说明:
如果 key 在字典中,返回它的值。如果不在,则插入键 key 并将其值设为 default,随后返回 default。default 默认是
None。
因此对上面的表达式:
>>> d = {i: i**2 for i in range(3)} # {0: 0, 1: 1, 2: 4}
>>> d.setdefault(2, 10)
4因为键 2 已经存在,setdefault 返回已有的值(4),且字典未被修改。
Why the Method Is Named setdefault
这个名字反映了该方法执行的两项操作:
- Set(设置)键的默认值 如果 该键缺失。
- Return(返回)键对应的值(无论是已经存在的还是刚刚设置的)。
而 .get(key, default) 只会获取值,setdefault 则保证调用后键一定存在,这在需要既获取又可能初始化值的代码中非常方便。
Typical Use Case
一个常见的模式是构建“字典‑列表”结构:
items_by_color = {}
items = [('duck', 'purple'), ('water bottle', 'purple'), ('uni-duck', 'pink')]
for item_name, item_color in items:
# 获取该颜色对应的列表,若不存在则设为新空列表并获取
items_list = items_by_color.setdefault(item_color, [])
items_list.append(item_name)
# Result: {'purple': ['duck', 'water bottle'], 'pink': ['uni-duck']}这里 setdefault 消除了在向列表追加元素前显式的 if … else 检查。
Performance Considerations
我比较了使用 setdefault 与显式 if … else 块的性能。
def test_no_set_default(sequence: dict) -> dict:
"""Re‑order incoming dict by value using if..else."""
reversed = {}
for k, v in sequence.items():
if v not in reversed:
reversed[v] = [k]
else:
reversed[v].append(k)
return reversed
def test_set_default(sequence: dict) -> dict:
"""Re‑order incoming dict by value using setdefault."""
reversed = {}
for k, v in sequence.items():
reversed.setdefault(v, [])
reversed[v].append(k)
return reversed生成随机数据的辅助函数:
from faker import Faker
def make_dict(items: int = 1000) -> dict:
"""Create a dictionary of random values."""
fake = Faker()
return {fake.name(): fake.color_name() for _ in range(items)}对两函数进行计时:
from timeit import timeit
def test_calls(times: int = 1_000_000):
setup_dict = make_dict()
t1 = timeit(lambda: test_no_set_default(setup_dict), number=times)
t2 = timeit(lambda: test_set_default(setup_dict), number=times)
print(f"no setdefault: {t1}")
print(f"with setdefault: {t2}")示例输出:
$ uv run python3 set_default_comparison.py --times 1000000
no setdefault: 100.65228875500907
with setdefault: 136.7275900120003在这个微基准测试中,setdefault 版本大约产生了 30 % 的额外开销。虽然对许多脚本来说差异可以忽略不计,但在性能关键的代码中可能会产生影响。
Conclusion
setdefault 将“初始化缺失键”和“返回键值”这两个动作合二为一,使代码更简洁、更具表现力。然而,与显式的 if … else 检查相比,它会带来一定的运行时成本。 当可读性和简洁性比微小的性能损失更重要时可以使用它;在对每微秒都计较的热点路径中则更倾向于使用显式模式。