Python的字典取值把我坑惨了,原来get和[]的区别这么大

简介: 本文通过小赵的踩坑经历,深入剖析Python字典取值中`[]`与`get()`的本质区别:`[]`是“断言键必存在”,缺失即快速报错;`get()`是“承认键可能不存在”,需谨慎处理`None`及默认值。厘清语义、按需选用,方能避免下游隐患。(239字)

小赵那天被一个线上告警折腾到凌晨两点。

他写了一个用户配置读取模块,从接口拿到一个字典,然后取里面的字段来用。逻辑很简单:

config = {"theme": "dark", "font_size": 14}

theme = config["theme"]
font_size = config["font_size"]
language = config["language"]  # 用户没设置这个字段

结果程序直接崩了,报了一个KeyError: 'language'。他盯着日志想:用户没设置语言,那给个默认值不就行了?于是他把代码改成了:

language = config.get("language")
print(language)  # None

程序不崩了。但他接着写:

if language.startswith("zh"):
   load_chinese_font()

又崩了,报AttributeError: 'NoneType' object has no attribute 'startswith'。他这才意识到,get虽然不报错,但返回的None会在下游埋下新的雷。

“我就想取个值,怎么这么难?”

问题的根源,是他没搞清楚get和[]的区别,以及它们各自应该在什么场景下用。

代理 IP 使用小技巧 让你的数据抓取效率翻倍 (98).png

先看一个最直观的对比

我们把两种取值方式放在一起跑一遍:

config = {"name": "Alice", "age": 30}

print(config["name"])       # Alice
print(config.get("name"))   # Alice

print(config.get("gender")) # None
print(config["gender"])     # KeyError

区别很清楚:

[]在键存在时正常返回,键不存在时直接抛KeyError,程序中断。

get在键存在时正常返回,键不存在时返回None,程序继续跑。

用一个生活场景来理解。你有一个储物柜,每个格子贴着标签。

[]的做法是:你直接伸手去摸“性别”那个格子。如果格子上贴着“性别”的标签,你拿到里面的东西。如果没有这个标签,你的手就撞在柜子上,当场“报警”。

get的做法是:你先问管理员“有没有‘性别’这个格子?”有就给你,没有就给你一个空盒子(None),你的手不会撞到柜子。

区别一目了然:一个会炸,一个不会炸。但不会炸不代表安全,因为空盒子可能在下游引发别的问题。

[] 的本质:断言“这个键一定存在”

很多人把config["key"]当成“取值”操作,但它其实隐含了一个断言:我确信这个键一定存在。

如果这个断言不成立,Python就抛KeyError。这不是bug,这是设计。它在告诉你:“你的假设错了,这个键根本不在字典里。”

这种“快速失败”在某些场景下是好事。比如你解析一个API返回的固定结构:

response = {"code": 200, "data": {"user_id": 123, "token": "abc"}}

user_id = response["data"]["user_id"]
token = response["data"]["token"]

如果API返回的数据结构变了,少了某个字段,你希望立刻知道,而不是悄悄拿到一个None然后在别的地方出错。KeyError会直接告诉你“字段没了”,定位问题很快。

所以[]适合你确定键存在的场景。它是一把锋利的刀,用对了干净利落,用错了直接见血。

get 的本质:我知道键可能不存在

get的签名是dict.get(key, default=None)。它接受一个键和一个可选的默认值。如果键存在,返回对应的值;如果键不存在,返回默认值(不传就是None)。

config = {"name": "Alice"}

print(config.get("name"))              # Alice
print(config.get("age"))               # None
print(config.get("age", 0))            # 0
print(config.get("age", "unknown"))    # unknown

get不会抛异常,它总是能返回点什么。这看起来很安全,但问题恰恰出在“总是能返回点什么”上。

因为None是一个合法的值,你无法区分“键不存在所以返回了None”和“键存在但值就是None”。

config = {"nickname": None}

print(config.get("nickname"))    # None
print(config.get("gender"))      # None

两个get都返回None,但一个是“用户设置了昵称为空”,另一个是“用户根本没设置性别”。如果你不区分这两种情况,后续逻辑就可能出错。

坑一:get 返回 None 后继续调用方法

小赵踩的就是这个坑。

language = config.get("language")
if language.startswith("zh"):
   load_chinese_font()

如果language不存在,get返回None,None.startswith直接报AttributeError。

正确的做法是给一个合理的默认值:

language = config.get("language", "en")
if language.startswith("zh"):
   load_chinese_font()

这样即使键不存在,language也是"en",不会崩。

或者先判断:

language = config.get("language")
if language and language.startswith("zh"):
   load_chinese_font()

if language既检查了None,也检查了空字符串。双重保险。

坑二:get 的默认值只在键不存在时生效

这是一个很微妙的点。get的默认值只在键不存在时生效,如果键存在但值是None,默认值不会生效。

config = {"timeout": None}

print(config.get("timeout", 30))  # None,不是 30

因为键"timeout"存在,get返回它的值None,默认值30被忽略。

如果你想要“值是None时也用默认值”,需要写成:

timeout = config.get("timeout") or 30

但or有个副作用:如果值是0、""、False这些“假值”,也会被替换成默认值。所以更严谨的写法是:

timeout = config.get("timeout")
if timeout is None:
   timeout = 30

这个细节在配置读取场景中特别常见。用户可能显式把某个字段设成了None,你的默认值逻辑要能正确处理。

坑三:嵌套字典取值

get只能处理一层。如果字典嵌套很深,get会变得很啰嗦。

data = {"user": {"profile": {"name": "Alice"}}}

# 想取 name,但不确定每一层是否存在
name = data.get("user", {}).get("profile", {}).get("name")

这行代码能跑,但很难看。每一层都要给一个空字典作为默认值,否则中间某一层返回None,下一层.get就会报AttributeError。

如果层级更深,或者键名不固定,这种写法就崩了。可以考虑用collections.defaultdict或者第三方库glom,但大多数时候,写个辅助函数就够了:

def deep_get(d, keys, default=None):
   for key in keys:
       if isinstance(d, dict):
           d = d.get(key)
       else:
           return default
       if d is None:
           return default
   return d

name = deep_get(data, ["user", "profile", "name"], "unknown")

坑四:用 [] 取值但没处理 KeyError

反过来,有些人习惯了[],在不确定键是否存在时也直接用,结果程序时不时崩一下。

for user in users:
   print(user["email"])  # 有些用户没有 email 字段

一旦某个用户没有email,整个循环就中断了。

这种场景应该用get:

for user in users:
   email = user.get("email", "未填写")
   print(email)

或者用in先判断:

for user in users:
   if "email" in user:
       print(user["email"])
   else:
       print("未填写")

in判断的好处是,你可以在两个分支里写不同的逻辑,而不是只拿到一个默认值。

那 setdefault 呢?

字典还有一个setdefault(key, default)方法,也常和get混淆。

config = {"name": "Alice"}
result = config.setdefault("age", 30)
print(result)   # 30
print(config)   # {'name': 'Alice', 'age': 30}

setdefault的行为是:如果键存在,返回对应的值,不修改字典;如果键不存在,把default插入字典,然后返回它。

和get的区别是:get不会修改字典,setdefault会。

config = {"name": "Alice"}

config.get("age", 30)
print(config)   # {'name': 'Alice'},没变

config.setdefault("age", 30)
print(config)   # {'name': 'Alice', 'age': 30},插入了

setdefault适合“取不到就设一个默认值并记住”的场景。比如统计词频:

counts = {}
for word in words:
   counts.setdefault(word, 0)
   counts[word] += 1

但这段代码其实可以用collections.Counter更简洁地实现。setdefault在现代Python里用得越来越少,因为大多数场景有更好的替代方案。

get 和 [] 的性能差异

有人可能会问:get和[]哪个快?

答案是:几乎一样快。两者都是哈希查找,时间复杂度都是O(1)。get多了一个“键不存在时返回默认值”的分支,但这个分支的开销可以忽略不计。

import timeit

d = {i: i for i in range(1000)}

t1 = timeit.timeit(lambda: d[500], number=1000000)
t2 = timeit.timeit(lambda: d.get(500), number=1000000)

print(f"[] 耗时: {t1:.4f}")
print(f"get 耗时: {t2:.4f}")

在我的机器上,两者都在0.03秒左右,差距在误差范围内。

所以选择get还是[],不应该基于性能,而应该基于语义:你确定键存在吗?如果确定,用[];如果不确定,用get。

什么时候用 [],什么时候用 get?

判断标准可以总结成一句话:你希望键不存在时发生什么?

如果你希望程序立刻报错,告诉你“假设错了”,用[]。这在解析固定结构的数据时很有用,能快速暴露问题。

如果你希望程序继续跑,用一个默认值兜底,用get。这在处理用户输入、可选配置、外部接口时很常见。

如果你需要区分“键不存在”和“键存在但值是None”,用in判断:

if "key" in d:
   value = d["key"]
   # 处理 value,可能是 None
else:
   # 键不存在

如果你需要在键不存在时插入一个默认值,用setdefault。

一个常见的误区:用 get 代替所有 []

有些人被KeyError坑过之后,走向另一个极端:所有取值都用get,再也不敢用[]。

name = config.get("name")
age = config.get("age")
email = config.get("email")

这样写的问题在于:如果name是必需的,但配置里漏了,get返回None,程序带着一个None继续跑,可能在很远的地方才崩。错误信息指向的是崩溃点,而不是根源。

而如果用[],程序在取name的那一刻就报KeyError: 'name',你立刻知道是配置缺了这个字段。

快速失败比带病运行好。 在关键字段上,用[]让问题尽早暴露;在可选字段上,用get提供合理的默认值。

回到小赵的代码

小赵最终的代码是这样的:

config = {"theme": "dark", "font_size": 14}

theme = config["theme"]                        # 必需字段,用 []
font_size = config.get("font_size", 12)        # 可选字段,用 get 带默认值
language = config.get("language", "en")        # 可选字段,默认英文

if language.startswith("zh"):
   load_chinese_font()

关键字段用[],缺失就报错,快速定位问题。可选字段用get,给一个合理的默认值,保证程序继续跑。

他在团队文档里写了一条:“[]是断言键存在,get是承认键可能不存在。想清楚你面对的是哪种情况,再决定用哪个。”

这个区分看起来简单,但真到写代码的时候,很多人还是会随手写get,然后在下游被None坑。或者随手写[],然后在生产环境被KeyError炸。

记住那个储物柜的比喻:[]是直接伸手去摸,摸不到就撞柜子;get是先问管理员,没有就给你个空盒子。你要做的,是在伸手之前想清楚:这个格子,我确定有吗?

确定,就用[]。不确定,就用get,并且想好默认值是什么。如果默认值None会在下游出问题,那就给一个真正安全的默认值,或者用in先判断。

字典取值不是难题,难的是想清楚“键不存在时该怎么办”。想清楚了,get和[]就不再是坑,而是两个各司其职的工具。

目录
相关文章
|
1月前
|
数据采集 监控 JavaScript
电商比价爬虫:同时爬取某东+某宝+某猫,实现全网比价
本文详解京东、淘宝、天猫三大平台反爬机制差异,提出“平台适配器+站大爷隧道代理”架构,涵盖Cookie管理、sign逆向、动态令牌、价格加密破解等关键技术,并提供可落地的多平台比价爬虫代码实现与避坑指南。(239字)
212 0
|
4月前
|
数据采集 Web App开发 JavaScript
房源信息采集:链家/贝壳等房产网站的反爬策略应对方案
本文详解链家/贝壳房产数据采集的反爬困境与实战方案:针对IP封禁、滑块验证、JS动态渲染及“幽灵房”假数据等难题,提出OpenClaw驱动真实浏览器+站大爷高匿隧道代理+请求频率与指纹伪装三重防护策略,兼顾稳定性与合规性。(239字)
663 0
|
3月前
|
数据采集 存储 前端开发
某乎爬虫进阶:爬取问题+回答+用户信息,构建知识图谱数据源
本文详解如何爬取知乎问题、回答及用户信息构建知识图谱:突破SSR渲染与反爬限制,采用API直调+Cookie认证+UA池+隧道代理方案;设计三表实体模型(问题/回答/用户),支持关系抽取与Neo4j存储,兼顾合规性与实用性。(239字)
306 2
|
1天前
|
存储 人工智能 自然语言处理
千问办公官网入口链接在哪?2026年最新QwenWork测评、千问办公怎么样?一文看懂
千问办公(QwenWork)是阿里云推出的AI智能办公平台,提供网页端和阿里云官网双入口。支持PPT/Word/Excel生成、多模态处理、网页全栈搭建、钉钉深度集成等6大核心能力。个人版免费可用,高级版198元/月;企业版198元/人/月。新用户注册即赠2000积分。阿里千问办公QwenWork官网:https://t.aliyun.com/U/0VCTGt 阿里AI工作平台,一句话完成数据分析、PPT 生成、视频剪辑、网页搭建等复杂任务
239 0
|
25天前
|
数据采集 JavaScript 前端开发
房产数据对比爬虫:同时爬取链家+贝壳+安居客,做房价横向对比
本文详解如何突破链家/贝壳(JS渲染+字体反爬)与安居客(动态Token+请求头校验)的差异化反爬机制,基于适配器模式构建跨平台爬虫架构,结合站大爷隧道代理实现IP隔离,并通过小区名+户型+面积模糊匹配,完成房价横向对比分析,助力识别虚高挂牌与低价引流。
222 1
|
3月前
|
数据采集 人工智能 自然语言处理
自动化比价系统:从采集到数据清洗,全链路打通教程
本文详解淘宝/京东/拼多多三平台自动化比价系统全链路:用OpenClaw自然语言采集、站大爷隧道代理防封(24小时成功率98.2%+)、AI智能清洗价格(统一格式、核销优惠、去重校验),自动生成可决策的比价报告与飞书预警,真正实现“采得稳、洗得准、用得上”。
348 1
|
4月前
|
人工智能 网络协议 Linux
禁用 IPv6:为什么关闭 IPv6 能提升 AI API 的稳定性
本文解析OpenClaw在Linux服务器上API调用不稳定的原因:IPv6双栈环境下DNS解析优先尝试IPv6,但国内IPv6路由、DNS及代理支持不完善,导致超时或失败。建议禁用IPv6,强制走更成熟的IPv4链路,并提供sysctl、GRUB、Docker等一键配置方案。(239字)
513 0
|
4月前
|
JavaScript 网络协议 Linux
踩坑记录:配置代理后无法联网?90%是HTTP/HTTPS协议搞混了
本文详解OpenClaw配置站大爷隧道代理后无法联网的典型问题,直指根源:HTTP/HTTPS协议混淆导致CONNECT请求失败。分享三种解决方案(推荐环境变量法),涵盖错误现象、原理分析与实操配置,助你快速避坑、节省调试时间。(239字)
297 0
|
12小时前
|
数据采集 人工智能 算法
2026年重庆GEO优化-生成式引擎优化产业范式与技术体系宏观研究报告
国内生成式AI用户达6.02亿,AI问答成主流信息获取方式。传统SEO失效,“搜索可见但AI不可见”成新痛点。GEO(生成式引擎优化)应运而生——聚焦国产大模型信源采信规则,构建权威、结构化、多源统一的可信信源矩阵,实现品牌在AI决策链路中的精准曝光与稳定输出。(239字)
27 0
|
4月前
|
Linux 数据库 iOS开发
Python的多进程居然把我坑惨了!别踩这个坑
本文详解Python多进程跨平台(Windows/Linux)的6大经典坑:启动方式差异(fork/spawn)、全局变量失效、pickle序列化失败、静默异常、多线程死锁及模块重复执行,并提供可落地的绕坑方案,助你一次写对、处处运行。
329 1

热门文章

最新文章