Python字典中setdefault与get的区别及使用场景对比
详解Python字典方法setdefault与get的核心区别,包括返回值、对原字典的影响、典型使用场景,并通过代码示例和对比图帮助读者选择合适的方法。
dict.get() 和 dict.setdefault() 都可以在访问字典键时提供默认值,但关键区别在于:get() 只读取,不改变字典;setdefault() 在键不存在时会把默认值写入字典。理解这一点,就能在不同场景下做出正确选择。下面从基本行为、差异对比、实际用法到选择依据,逐步展开说明。
get方法的基本行为
dict.get(key, default=None) 尝试返回 key 对应的值。如果 key 不存在,返回 default 参数指定的值(默认为 None),字典本身不会发生任何变化。
config = {"host": "localhost", "port": 8080}
# 键存在,返回对应值
host = config.get("host", "0.0.0.0")
print(host) # localhost
# 键不存在,返回默认值
timeout = config.get("timeout", 30)
print(timeout) # 30
# 字典没有变化
print(config) # {'host': 'localhost', 'port': 8080}
30
{'host': 'localhost', 'port': 8080}
可以看到,get() 返回了默认值 30,但 config 中并没有新增 "timeout" 这个键。这正是 get() 的设计意图——安全地读取,不产生副作用。
setdefault方法的基本行为
dict.setdefault(key, default=None) 的逻辑分两种情况:如果 key 已存在,直接返回已有值,不做修改;如果 key 不存在,先将 key: default 写入字典,再返回 default。
config = {"host": "localhost", "port": 8080}
# 键存在,返回已有值,不覆盖
host = config.setdefault("host", "0.0.0.0")
print(host) # localhost
# 键不存在,写入默认值并返回
timeout = config.setdefault("timeout", 30)
print(timeout) # 30
# 字典已新增 timeout
print(config) # {'host': 'localhost', 'port': 8080, 'timeout': 30}
30
{'host': 'localhost', 'port': 8080, 'timeout': 30}
与 get() 不同,setdefault() 会在键缺失时真正修改字典。这在需要"确保键存在"的场景中特别有用。
核心区别对比
两个方法在调用形式上很相似,容易混淆。下面从几个维度逐一对比,帮助形成清晰的判断。
两者在"键已存在"时行为完全一致,区别只在键不存在的那一刻:get() 不碰字典,setdefault() 会把默认值填进去。
get的典型使用场景
安全取值,避免 KeyError
最常见的用法是用 get() 替代直接下标访问,防止键不存在时抛出 KeyError:
user = {"name": "张三", "age": 28}
# 直接访问不存在的键会报错
# print(user["email"]) # KeyError: 'email'
# get 安全取值
email = user.get("email", "未提供")
print(email) # 未提供
条件逻辑中的临时替代值
当你只需要一个替代值来参与判断或计算,不希望改变原始数据时,get() 是最合适的选择:
scores = {"math": 92, "english": 85}
# 缺失科目按0分计算总分,但不在字典里添加记录
total = scores.get("math", 0) + scores.get("english", 0) + scores.get("physics", 0)
print(total) # 177
print(scores) # {'math': 92, 'english': 85} — 没有 physics
从嵌套字典中安全提取数据
处理API返回的JSON数据时,多层 get() 可以避免中间某层为 None 时出错:
response = {"data": {"user": {"name": "李四"}}}
# 逐层安全取值
name = response.get("data", {}).get("user", {}).get("name", "匿名")
print(name) # 李四
# 如果某一层缺失
response2 = {"data": {}}
name2 = response2.get("data", {}).get("user", {}).get("name", "匿名")
print(name2) # 匿名
setdefault的典型使用场景
分组归类——按键收集列表
这是 setdefault() 最经典的用法。在遍历数据、按某个字段分组时,需要为每个新分组自动创建空列表:
records = [
{"dept": "技术部", "name": "王五"},
{"dept": "市场部", "name": "赵六"},
{"dept": "技术部", "name": "钱七"},
{"dept": "市场部", "name": "孙八"},
{"dept": "人事部", "name": "周九"},
]
groups = {}
for r in records:
groups.setdefault(r["dept"], []).append(r["name"])
print(groups)
# {'技术部': ['王五', '钱七'], '市场部': ['赵六', '孙八'], '人事部': ['周九']}
这里 setdefault(r["dept"], []) 完成了两件事:如果部门键不存在,插入空列表并返回它;如果已存在,直接返回已有列表。紧接着 .append() 往这个列表里添加姓名。如果用 get(),则无法在同一行内完成这个操作,因为 get() 返回的列表不会被字典记住。
配置合并——只填充缺失项
当你有一组默认配置,需要与用户传入的配置合并,但不能覆盖用户已有的设置时:
user_config = {"theme": "dark", "lang": "zh-CN"}
defaults = {"theme": "light", "lang": "en", "font_size": 14, "show_line_num": True}
for key, value in defaults.items():
user_config.setdefault(key, value)
print(user_config)
# {'theme': 'dark', 'lang': 'zh-CN', 'font_size': 14, 'show_line_num': True}
用户指定的 theme 和 lang 保持不变,只有缺失的 font_size 和 show_line_num 被填入默认值。
构建字典中的嵌套结构
在逐步构建复杂嵌套字典时,setdefault() 可以避免反复判断中间层是否存在:
data = {}
# 逐层确保键存在
data.setdefault("server", {}).setdefault("database", {})["host"] = "127.0.0.1"
data.setdefault("server", {}).setdefault("database", {})["port"] = 3306
print(data)
# {'server': {'database': {'host': '127.0.0.1', 'port': 3306}}}
容易混淆的地方
用 get 替代 setdefault 做分组会怎样?
有些初学者会写出类似下面的代码:
# 错误示范
groups = {}
for r in records:
groups.get(r["dept"], []).append(r["name"])
print(groups) # {} — 空的!
问题在于 get() 每次返回一个新的空列表,但没有将它存入 groups,所以 append() 操作的列表立即被丢弃。这是 get() 和 setdefault() 最容易踩的坑。
setdefault 不会覆盖已有值
这是另一个常见误解。setdefault() 只在键不存在时写入,已有的值不会被替换:
d = {"color": "red"}
d.setdefault("color", "blue")
print(d["color"]) # red — 仍然是原来的值
如果你的目的是"无论如何都设置这个值",应该直接赋值 d["color"] = "blue"。
setdefault 与 collections.defaultdict
setdefault() 与 defaultdict 在分组场景中作用类似,但有几点区别:
defaultdict在任何键访问时都会自动创建默认值(包括意外访问),setdefault()只在你显式调用时才触发defaultdict的默认值类型在创建时确定,setdefault()可以针对不同键给不同的默认值- 如果只在某一处需要分组,
setdefault()更轻量;如果整个字典都需要自动初始化,defaultdict更方便
如何选择:一个简单的判断标准
在写代码时,可以问自己一个问题:这次访问之后,我是否需要字典里"记住"这个默认值?
- 如果答案是不需要——只是临时取个值用一下,用
get() - 如果答案是需要——后续代码还会依赖这个键的存在,用
setdefault()
举几个具体例子帮助判断:
| 场景 | 选择 | 原因 |
|---|---|---|
| 显示用户昵称,没有则显示"游客" | get() | 只是展示,不需要把"游客"存进数据 |
| 统计单词频次,首次出现时初始化为0 | setdefault() | 后续要在此基础上累加 |
| 读取配置中的超时时间,缺失时用默认值30 | get() | 不应该修改原始配置数据 |
| 将日志按等级分组到不同列表 | setdefault() | 需要字典持续积累分组结果 |
| 合并默认配置到用户配置中 | setdefault() | 需要把缺失的默认值写入用户配置 |
综合示例:在同一段代码中使用两者
下面的例子模拟处理一批学生考试成绩,展示在同一个任务中如何自然地混合使用两种方法:
exam_results = [
{"name": "张三", "subject": "数学", "score": 92},
{"name": "李四", "subject": "数学", "score": 85},
{"name": "张三", "subject": "英语", "score": 78},
{"name": "王五", "subject": "数学", "score": 96},
{"name": "李四", "subject": "英语", "score": 91},
]
# 按学生姓名分组(需要持久化 → setdefault)
student_scores = {}
for result in exam_results:
student_scores.setdefault(result["name"], []).append(
{"subject": result["subject"], "score": result["score"]}
)
# 生成报告时安全取值(只读 → get)
for name, scores in student_scores.items():
math_score = None
for s in scores:
if s["subject"] == "数学":
math_score = s["score"]
break
# 没有数学成绩时显示"未参加",但不写入原数据
display_math = math_score if math_score is not None else "未参加"
print(f"{name}:数学 {display_math}")
李四:数学 85
王五:数学 96
分组阶段用 setdefault(),因为需要字典记住每个学生的成绩列表;展示阶段用安全取值逻辑,因为缺失数据只需显示替代文本,不应改动原始数据。
get() 是只读的安全取值,setdefault() 是"读取+按需初始化"的组合操作。两者不是替代关系,而是各有明确的适用场景。记住一个判断原则:如果你只是需要一个临时的替代值,用 get();如果你需要字典在这次操作后确实拥有这个键,用 setdefault()。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









