📌 写在前面:为什么dict是Pythoner的必修课?
哈喽各位Python小伙伴们~ 👋 今天咱们来聊一聊Python中最最最常用的数据结构之一------dict(字典)!
说到dict啊,那可真是Python界的"万能工具箱"🧰,不管是存配置、做缓存、转JSON、统计计数,几乎没有它干不了的活儿。但是!你真的完全搞懂dict了吗?
灵魂拷问三连:
-
dict到底继承自谁?它和list是"远房亲戚"吗?🤔
-
浅拷贝和深拷贝到底有啥区别?为啥改了新字典旧的也跟着变?😱
-
setdefault和get到底差在哪?update居然有三种写法?🤯
别慌!今天这篇文章,咱们就从底层继承关系 讲到全部常用方法 ,再配上性能小Tips,保证你看完之后,dict用得那叫一个炉火纯青!🔥 坐稳扶好,咱们发车啦~ 🚀
Bilibili 同步视频
🧬 第一部分:dict的"血统"探秘------抽象基类ABC继承关系
1.1 两大抽象基类:Mapping & MutableMapping
首先,咱们得搞清楚一个问题:Python的dict,它到底是个啥"出身"?
在Python的 collections.abc 模块里,藏着两个非常重要的抽象基类------Mapping 和 MutableMapping。它们就像是dict的"族谱"上的两位老祖宗~ 👴👵
python
from collections.abc import Mapping, MutableMapping
咱们来梳理一下这俩的关系:
| 抽象基类 | 核心特性 | 代表方法 |
|---|---|---|
| Mapping | 只读映射,只能查不能改 | get、keys、items、values、getitem |
| MutableMapping | 可变映射,可读可写可删 | pop、update、setdefault、clear、setitem |
而咱们的 dict,它就属于 MutableMapping 类型!毕竟字典嘛,肯定得能增删改查才行~
1.2 isinstance判断的"坑":类 vs 实例
说到类型判断,这里有个非常经典的小坑,很多新手小伙伴都踩过!🕳️
❌ 错误写法:直接把dict类传进去
python
# 这样写是错的!返回 False
print(isinstance(dict, MutableMapping)) # False
为啥是False呢?因为 isinstance() 判断的是实例的类型,而不是类本身!dict是一个类,不是实例,当然不对啦~
✅ 正确写法:创建一个字典实例再判断
python
a = {"name": "imock"}
print(isinstance(a, MutableMapping)) # True
划重点!isinstance第一个参数必须是实例对象,不是类!这个坑记好了,面试常考哦~ 🎯
1.3 底层揭秘:不是继承,是"注册"!
说到这里,可能有小伙伴会问:"那dict是继承了MutableMapping吗?" 哎,这个问题问得好!👍
答案是------并没有! 😮
Python的dict底层是用C语言写的,它并没有在语法层面去继承MutableMapping这个抽象基类。那为什么isinstance还能返回True呢?
秘密就在于Python的**注册机制(register)**和classhook钩子函数!
简单来说就是:dict虽然没有直接继承,但是它实现了MutableMapping协议中规定的所有魔法函数(比如__getitem__、setitem、__len__等等),然后通过register机制"登记在册",所以isinstance就认它是MutableMapping的"自己人"啦~ 🤝
**一句话总结:**dict和list都实现了顶层的Collection协议,所以它们有很多通用操作;但dict额外实现了Mapping协议,拥有键值对的专属能力。这就是Python的"鸭子类型"哲学------走起来像鸭子,叫起来像鸭子,那它就是鸭子!🦆
🛠️ 第二部分:dict常用方法大全(核心干货!)
好了,血统搞清楚了,接下来咱们进入重头戏------dict的那些实用方法!💪
很多小伙伴觉得dict不就是增删改查吗?那你可就太小瞧它了!Python的dict藏着很多"黑科技",用好了能让你的代码既优雅又高效~
2.1 clear():一键清空,干干净净
第一个方法非常简单------clear(),顾名思义,就是清空字典里所有的键值对。
python
# 创建一个字典
a = {"bob1": "imock", "bob2": "imock2"}
# 清空字典
a.clear()
print(a) # {}
⚠️ 注意:clear()是原地修改,没有返回值(或者说返回None)。它直接把原字典清空了,不会给你返回一个新的空字典哦~
2.2 copy():深浅拷贝的那些"坑",90%的人都踩过!
接下来这个方法------copy(),看似简单,实则暗藏玄机!这绝对是dict方法中坑最多的一个,没有之一!😤
什么是浅拷贝?
dict.copy() 返回的是一个浅拷贝(shallow copy)。什么意思呢?
简单来说:
-
✅ 顶层的键值对会复制一份,新字典和原字典是独立的容器
-
❌ 但是!如果值是嵌套的可变对象(比如内层的dict、list),只会复制引用,不会复制内容!
说人话就是:外壳是新的,里面的东西还是原来的那些~ 📦
python
# 原字典,值是嵌套的字典
a = {"bob1": {"company": "imock"}}
# 浅拷贝
new_dict = a.copy()
# 修改新字典里的嵌套字典
new_dict["bob1"]["company"] = "imock3"
# 猜猜看,原字典a变了吗?
print(a) # {'bob1': {'company': 'imock3'}}
# 啊!原字典居然也变了!😱
**坑点预警!**浅拷贝只拷贝了最外层,内层的可变对象还是共享同一份内存。改一个,另一个也跟着变,这个bug能让你找半天!
深拷贝:彻底解决嵌套问题
那如果我想要完全独立的拷贝怎么办?这时候就需要**深拷贝(deep copy)**了!
深拷贝会递归地拷贝所有层级的数据,完完全全复制一份,新的和旧的彻底没关系,老死不相往来~ 👋
python
import copy
a = {"bob1": {"company": "imock"}}
# 深拷贝
new_dict = copy.deepcopy(a)
# 修改新字典
new_dict["bob1"]["company"] = "imock3"
# 原字典不受影响!
print(a) # {'bob1': {'company': 'imock'}}
print(new_dict) # {'bob1': {'company': 'imock3'}}
**性能小Tips:**深拷贝因为要递归遍历所有数据,所以性能比浅拷贝差很多。如果你的字典里没有嵌套的可变对象,用浅拷贝就够了,又快又香~ 只有在确实需要完全隔离的时候,才用深拷贝。
2.3 fromkeys():批量造字典神器
接下来介绍一个非常好用的静态方法------fromkeys()!
想象一下这个场景:你有一个列表,里面是一堆key,你想把它们都变成字典的键,并且统一赋一个默认值。怎么办?循环遍历一个个加?太low了!👎
用 dict.fromkeys(),一行搞定!✨
python
# 有一个key列表
key_list = ["bob1", "bob2", "bob3"]
# 批量创建字典,所有key的默认值都是"imock"
new_dict = dict.fromkeys(key_list, "imock")
print(new_dict)
# {'bob1': 'imock', 'bob2': 'imock', 'bob3': 'imock'}
语法:dict.fromkeys(可迭代对象, 默认值)
第一个参数可以是任意可迭代对象------list、tuple、set、甚至另一个字典的keys,统统都可以!非常灵活~ 🎯
**注意坑点:**如果默认值是可变对象(比如空列表\[\]),所有的key都会共享同一个对象!改一个全变。这时候建议用字典推导式或者循环来创建。
2.4 get():告别KeyError的安全访问方式
接下来这个方法------get(),可以说是dict方法中使用频率最高的一个了!强烈建议所有人都掌握!🔥
为什么需要get?
咱们平时访问字典,最常用的就是方括号语法:
python
d = {"name": "imock"}
print(d["name"]) # 正常
print(d["age"]) # KeyError!💥
但是!如果key不存在,直接就给你抛个 KeyError,程序直接崩给你看~ 😭 在实际开发中,这可是高频崩溃来源之一!
get的正确打开方式
用 get() 就安全多了:key存在就返回对应的值,不存在就返回默认值,绝对不报错!
python
d = {"name": "imock"}
# key存在,正常返回值
print(d.get("name", "默认值")) # imock
# key不存在,返回默认值,不报错
print(d.get("age", "未知")) # 未知
# 不传默认值的话,key不存在返回None
print(d.get("gender")) # None
**最佳实践:**凡是你不确定key是否存在的场景,一律用get()!尤其是处理接口返回数据、用户输入等不可信数据源时,get()能帮你规避99%的KeyError。写代码稳如狗~ 🐶
2.5 items() / keys() / values():遍历三件套
这三个方法大家应该都很熟悉了,遍历字典的时候天天用~
python
d = {"name": "imock", "age": 18, "city": "北京"}
# 遍历键值对(最常用!)
for k, v in d.items():
print(f"{k}: {v}")
# 只遍历键
for k in d.keys():
print(k)
# 只遍历值
for v in d.values():
print(v)
这里有个小知识点:items()返回的是元组拆包 的形式,所以我们可以直接用 for k, v in d.items() 这种写法,非常优雅~
2.6 setdefault():取值+赋值一步到位,效率更高!
接下来这个方法------setdefault(),可以说是dict方法中的宝藏方法!很多人都不知道它,但它真的超级好用!💎
setdefault做了什么?
简单来说,setdefault干了两件事:
-
先查找key,如果存在,直接返回对应的值(和get一样)
-
如果key不存在------不仅返回默认值,还会把这个key和默认值存进字典里!
对比一下:
| 方法 | key不存在时返回默认值 | key不存在时写入字典 |
|---|---|---|
| get() | ✅ 是 | ❌ 否 |
| setdefault() | ✅ 是 | ✅ 是 |
python
d = {"name": "imock"}
# key不存在,返回默认值,并且写入字典
result = d.setdefault("age", 18)
print(result) # 18
print(d) # {'name': 'imock', 'age': 18}
# key已存在,直接返回已有值,不修改
result2 = d.setdefault("name", "其他值")
print(result2) # imock
print(d) # {'name': 'imock', 'age': 18} 没变
setdefault的经典应用场景
setdefault最经典的应用就是分组统计!比如你想把一个列表按某个条件分组,存到字典里:
python
# 传统写法(啰嗦版)
result = {}
for item in data_list:
key = item["category"]
if key not in result:
result[key] = []
result[key].append(item)
# setdefault写法(优雅版)
result = {}
for item in data_list:
key = item["category"]
result.setdefault(key, []).append(item)
**性能小Tips:**setdefault是Python内置的C级实现,比你自己写"if判断+赋值"的步骤更少,性能更高!尤其是在大数据量循环中,差距会更明显。又优雅又快,何乐而不为呢~ 😎
2.7 update():字典合并的N种姿势
最后,咱们来聊聊 update() 方法------字典合并神器!
update的作用就是把另一个字典(或者可迭代对象)的键值对,合并更新到当前字典里。key相同的话,新值会覆盖旧值。
但是!你知道update有三种传参方式吗?很多人只知道第一种~ 🤫
姿势一:传入另一个字典
python
d = {"name": "imock"}
d.update({"age": 18, "city": "北京"})
print(d) # {'name': 'imock', 'age': 18, 'city': '北京'}
姿势二:关键字参数传参
python
d = {"name": "imock"}
d.update(age=18, city="北京", gender="男")
print(d)
# {'name': 'imock', 'age': 18, 'city': '北京', 'gender': '男'}
这种写法是不是更简洁?直接写key=value就行,不用加引号、不用写大括号,爽得很~ 🎉
姿势三:传入嵌套元组的可迭代对象
python
d = {"name": "imock"}
# 列表包元组
d.update([("age", 18), ("city", "北京")])
# 元组包元组也行
d.update((("gender", "男"), ("job", "程序员")))
print(d)
# {'name': 'imock', 'age': 18, 'city': '北京', 'gender': '男', 'job': '程序员'}
为啥这种方式也行呢?因为update的本质是遍历一个可迭代对象,然后把每个元素当成(key, value)对来处理。只要是可迭代的,里面装的是二元组,统统都可以!
**Python哲学:**这就是Python的"鸭子类型"思想的体现------不关心你是什么类型,只关心你能不能迭代、能不能拆成两个元素。灵活得很~ 🦆
⚡ 第三部分:dict性能小Tips,让你的代码飞起来!
聊了这么多方法,最后给大家分享几个dict的性能优化小技巧,都是干货!📦
3.1 优先使用内置方法,少自己写逻辑
Python的dict内置方法(get、setdefault、update等)都是用C语言实现的,比你自己用Python写if判断、循环赋值快得多。
python
# 慢!Python层的判断+赋值
if key not in d:
d[key] = value
val = d[key]
# 快!C语言级别的内置实现
val = d.setdefault(key, value)
3.2 字典推导式 vs 循环赋值
创建字典的时候,优先用字典推导式,比循环append快不少:
python
# 推荐:字典推导式
result = {k: v for k, v in data if condition}
# 不推荐:循环赋值
result = {}
for k, v in data:
if condition:
result[k] = v
3.3 深浅拷贝的选择
-
没有嵌套可变对象 → 用
copy()浅拷贝,快 -
有嵌套且需要完全隔离 → 用
copy.deepcopy()深拷贝,安全但慢 -
只是想传参避免修改原字典 → 考虑是否真的需要拷贝,很多时候不需要
3.4 大字典的key查询
dict的key查询是O(1)的哈希查找,非常快。但如果你要反复判断某个key是否存在,用 in 操作符就够了,不要傻乎乎地遍历keys():
python
# ✅ 推荐:O(1) 哈希查找
if key in d:
...
# ❌ 不推荐:O(n) 遍历,蠢
if key in d.keys():
...
📝 总结:dict方法速查表
最后,给大家整理了一张dict常用方法速查表,建议收藏备用~ 📌
| 方法 | 作用 | 推荐指数 |
|---|---|---|
| clear() | 清空字典所有键值对 | ⭐⭐⭐ |
| copy() | 浅拷贝字典(嵌套对象共享引用) | ⭐⭐⭐⭐ |
| fromkeys() | 用可迭代对象批量创建字典 | ⭐⭐⭐ |
| get() | 安全取值,key不存在返回默认值 | ⭐⭐⭐⭐⭐ |
| items() | 返回所有键值对,用于遍历 | ⭐⭐⭐⭐⭐ |
| keys() | 返回所有键 | ⭐⭐⭐⭐ |
| values() | 返回所有值 | ⭐⭐⭐⭐ |
| pop() | 删除指定key并返回值 | ⭐⭐⭐⭐ |
| setdefault() | 取值+不存在则写入,一步到位 | ⭐⭐⭐⭐⭐ |
| update() | 合并更新字典(三种传参方式) | ⭐⭐⭐⭐⭐ |
🎯 写在最后
好啦,今天关于Python dict的分享就到这里啦~ 🎉
回顾一下,咱们从dict的抽象基类继承关系讲起,深挖了isinstance的坑,揭秘了注册机制的底层原理;然后详细讲解了clear、copy、fromkeys、get、setdefault、update等核心方法,还特别强调了浅拷贝深拷贝这个经典坑点;最后还给大家分享了几个性能优化小技巧。
怎么样?是不是感觉对dict的理解又深了一层?😎
**互动时间:**你在使用dict的过程中还遇到过哪些坑?或者有什么独门绝技?欢迎在评论区留言分享,大家一起交流进步~ 👇

如果这篇文章对你有帮助,别忘了 点赞👍 + 收藏⭐ + 关注👀 三连支持一下哦!你们的支持就是我更新的最大动力~ 笔芯!❤️
我是郝学胜,一个专注于Python技术分享的程序员。咱们下篇文章再见~ 👋👋👋