Python 高级编程 028:字典dict从入门到精通

📌 写在前面:为什么dict是Pythoner的必修课?

哈喽各位Python小伙伴们~ 👋 今天咱们来聊一聊Python中最最最常用的数据结构之一------dict(字典)

说到dict啊,那可真是Python界的"万能工具箱"🧰,不管是存配置、做缓存、转JSON、统计计数,几乎没有它干不了的活儿。但是!你真的完全搞懂dict了吗?

灵魂拷问三连:

  • dict到底继承自谁?它和list是"远房亲戚"吗?🤔

  • 浅拷贝和深拷贝到底有啥区别?为啥改了新字典旧的也跟着变?😱

  • setdefault和get到底差在哪?update居然有三种写法?🤯

别慌!今天这篇文章,咱们就从底层继承关系 讲到全部常用方法 ,再配上性能小Tips,保证你看完之后,dict用得那叫一个炉火纯青!🔥 坐稳扶好,咱们发车啦~ 🚀


Bilibili 同步视频

Python 高级编程 028:字典dict从入门到精通


🧬 第一部分:dict的"血统"探秘------抽象基类ABC继承关系

1.1 两大抽象基类:Mapping & MutableMapping

首先,咱们得搞清楚一个问题:Python的dict,它到底是个啥"出身"?

在Python的 collections.abc 模块里,藏着两个非常重要的抽象基类------MappingMutableMapping。它们就像是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干了两件事

  1. 先查找key,如果存在,直接返回对应的值(和get一样)

  2. 如果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技术分享的程序员。咱们下篇文章再见~ 👋👋👋

相关推荐
smallswan1 小时前
《Rust七十二变》开源了
开发语言·后端·rust·ai编程
weixin_BYSJ19871 小时前
springboot小区物业管理小程序---附源码45555
java·javascript·spring boot·python·django·flask·php
shepherd1111 小时前
国产模型越来越强了:DeepSeek V4、Kimi K3 与 GLM 最新进展
人工智能·llm·ai编程
颜进强1 小时前
11 - 从需求拆解到 OpenSpec:为什么不要直接敲 /opsx:explore
前端·后端·ai编程
Sammyyyyy2 小时前
AI Gateway 与直连 LLM API 的应该怎么选,一篇文章说明白
人工智能·ai·gateway·ai编程·ai-native·servbay
a-6262 小时前
三维重建的步骤-1(采集的是环绕物体均匀旋转一圈的视频)
python·pycharm·三维重建·metashape
129Lab2 小时前
数据驱动电池SOH预测实战:用AI工具快速构建深度学习模型并调参
pytorch·深度学习·ai编程·数据驱动·特征工程·电池管理系统·电池健康状态
qq_22589174662 小时前
基于Flask的空气质量监测与预测分析系统
开发语言·后端·python·信息可视化·django·flask
AINative软件工程3 小时前
LLM 应用的 Warm-Up 工程实践:冷启动延迟从 12 秒砍到 800ms 的 5 个工程手段
后端·llm·ai编程