setdefault 是一个丑陋的 dict 方法

发布: (2026年3月23日 GMT+8 18:30)
4 分钟阅读
原文: Dev.to

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,随后返回 defaultdefault 默认是 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

这个名字反映了该方法执行的两项操作:

  1. Set(设置)键的默认值 如果 该键缺失。
  2. 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 检查相比,它会带来一定的运行时成本。 当可读性和简洁性比微小的性能损失更重要时可以使用它;在对每微秒都计较的热点路径中则更倾向于使用显式模式。

0 浏览
Back to Blog

相关文章

阅读更多 »