
1. 字典是什么和列表到底差在哪如果你刚开始学Python那列表list一定是你最先接触的数据结构之一。什么[1, 2, 3]、[苹果, 香蕉]往里面塞东西按下标取出来逻辑简单到不需要动脑子。但一旦你开始做稍微有点实际意义的事情比如统计一篇文章里每个单词出现了几次或者维护一份配置信息再或者处理从数据库查出来的用户记录你就会发现列表为主的思路开始给你添堵了。你要用一个列表存所有人的成绩然后在这个列表里按某种规则找到张三的成绩最直白的写法是遍历一遍。数据量小无所谓数据量一上来每次查找都是从头扫到尾时间成本直接起飞。而且代码的可读性也差你写那个遍历循环的瞬间心里其实冒出来一个念头这不是我想要的有没有一种结构可以让我直接通过一个名字一把拿到对应的成绩有的这是字典dict在做的事情。字典是一种键值对key-value pair的数据结构它跟我们小时候查纸质词典的行为模式完全一致——我要查Python这个单词的意思我不会从第一页开始翻我会直接根据拼音或部首定位到那一页找到这个词条然后读它旁边的解释。在Python字典里这个定位过程是直接算出来的不是靠遍历这就是它和列表最核心的分水岭。从日常使用的角度理解列表是一串数据排队站好拿编号来认人字典是一张登记表娶一个名字就给你对应的人。还有几个和列表的关键区别建议你在一开始就消化掉字典里的元素是成对出现的key: value一个键对应一个值键和值中间用冒号隔开一对一对放在花括号里。键必须是不可变类型数字、字符串、元组都可以做键列表这种可变类型不行。键是唯一的同一个字典里不可能出现两个相同的键。你往里面重复赋值新值会直接覆盖旧值。字典本身是可变类型你可以往里面加数据、改数据、删数据这种可变是深层的跟列表可变一样涉及引用传递时需要多加留意。一句话总结列表的本质是序列字典的本质是映射。所谓映射就是给定一个输入给你一个对应输出这种思想在编程里到处都是字典正是Python对这种思想最直接的支持。2. 构建字典的几种姿势以及各自的适用场景字典的构建方法看起来很多不同的写法在可读性、性能和使用场景上其实有一些微妙的差别。我把常见的完整过一遍。2.1 字面量直接创建这是最简单的写法也是最推荐的写法。花括号包住一堆用冒号连接的键值对键值对之间用逗号分隔score {张三: 92, 李四: 85, 王五: 96}这种写法的好处是结构一目了然一眼就能看出哪个键对应哪个值。正常情况下如果键和值在代码里是写在明处的就用这种字面量方式创建。2.2 从零开始往空字典里添加先声明一个空字典然后往里面塞键值对score {} score[张三] 92 score[李四] 85注意{}创建的是空字典不是空集合。空集合要用set()。这个点做过开发的人基本都犯过迷糊我用过一次{}然后往里面 add 元素直接报错才反应过来这是个字典。这种方式的适用场景是数据是动态产生的你不能在写代码的时候就把内容定死。比如从循环里读取数据每次算出一个结果就往字典里填。2.3 用 dict() 构造器创建dict()构造器有几种不同的用法。最常见的是传入关键字参数user dict(name张三, age18, city北京)这种写法把键写成了变量名的形式所以键必须满足变量名规则不能有什么特殊字符也不能是数字开头。如果键是字符串且符合变量名规范这种写法挺清爽的。但要构造带短横线、空格或者数字的键就不行了。还可以传一个二元组组成的可迭代对象pairs [(name, 张三), (age, 18)] user dict(pairs)这种写法在把其他格式的数据转换成字典时很有用比如从某个外部系统拿到这种配对数据需要快速转成字典。2.4 用 fromkeys 批量创建dict.fromkeys(keys, value)可以从一个列表或元组批量创建字典所有键共享同一个初始值。如果只传键列表不传第二个参数所有键默认指向Nonekeys [apple, banana, cherry] fruit_count dict.fromkeys(keys, 0) # 结果: {apple: 0, banana: 0, cherry: 0}这里要提醒一点如果你传的第二个参数是一个可变对象比如[]或{}那么所有键指向的是同一个对象。这是很多写初始化逻辑的人容易踩的坑。比如你想给每个键准备一个空列表用于之后分别填充数据写dict.fromkeys(keys, [])看起来没毛病实际上所有键指向同一个列表往任何一个键里加东西其他键的内容也跟着变整个就乱了。遇到这种情况需要用字典推导式单独创建新的列表对象。2.5 字典推导式字典推导式是列表推导式的亲戚把花括号里的表达式变成key表达式: value表达式的形式squares {x: x * x for x in range(1, 6)} # 结果: {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}推导式最大的价值是可以用循环逻辑来构建字典还能加条件判断。比如你有一串商品价格想要滤掉低于某个值的商品同时做格式整理prices {苹果: 3.5, 香蕉: 5.0, 葡萄: 12.0, 梨: 2.8} affordable {name: price for name, price in prices.items() if price 5}一行代码搞定可读性也不错。我自己在实际编码的时候这种场景几乎都优先考虑推导式。2.6 zip 组合两个序列如果键列表和值列表是两条平行序列可以用zip把它们捏在一起names [张三, 李四, 王五] scores [92, 85, 96] score dict(zip(names, scores))这条写法的适用场景是数据天然分成两组比如两个字段分别从两列数据里读出来要合成一个映射关系。构建方式小结小批量的、静态的用字面量动态增长的用空字典加赋值批量同值初始化用fromkeys但注意可变对象陷阱从一个可迭代结构转换用dict()加元组配对或zip需要依据规则批量生成用推导式。3. 核心操作详解增删改查的正确姿势字典的各种操作看起来都很简单但里面有一些细节在日常开发中会成为隐性的性能瓶颈或Bug源头。我把使用频率最高的操作逐个拆开讲。3.1 基本访问与 KeyError 问题访问字典里某一个值最直接的写法是score {张三: 92, 李四: 85} print(score[张三]) # 92如果键不存在这一句就会抛出KeyError。很多人刚开始写代码时被这个报错支配过其实这是Python的一种安全保护机制宁可显式报错告诉你键不存在也不能默默返回一个什么空值误导你。但换到某些业务场景比如读取一个用户字典有些字段可能是可选的没有不可怕但你不想让它崩掉。这时候有几种处理方式用in先判断if 李四 in score: print(score[李四])用get()方法不存在的键返回None或指定默认值print(score.get(赵六)) # None print(score.get(赵六, 0)) # 0get()在多数场景是更好的选择一次调用不需要先in再访问代码简洁也不容易出现判断完键存在结果另一个线程给删了这种并发问题。只是在文本型代码逻辑中这就是个习惯问题但在并发环境中get避免了一整个竞态窗口。3.2 新增与修改底层是同一种操作往字典里加一个新的键值对score[赵六] 78如果要修改一个已有键的值也是同样的语法score[张三] 95也就是说赋值操作符对字典来说是键不存在就创建键存在就更新。这个语义很容易理解但有一个隐藏的性能细节在写法上完全相同Python解释器不需要在内部去区分我要新增还是更新你写得一样就行了。如果需要一次性把另一个字典的所有键值对合并过来可以用update()方法new_scores {孙七: 88, 李四: 90} score.update(new_scores) # 效果孙七新增李四的值被更新为 90update()还可以传关键字参数或可迭代的配对序列用法相当自由。3.3 删除操作的三兄弟del、pop、popitem删除键值对首选deldel score[张三]注意如果键不存在del一样会抛KeyError有洁癖的话可以先判断一下。pop(key)可以把某个键对应的值取出来同时把这个键值对删掉。这个能力在我需要这个值做后续处理但用完之后字典里不再需要它的场景下非常好用name score.pop(李四)和del不同pop还支持默认值参数如果键不存在不会抛异常而是返回默认值。这又是一个比先判断再删除更优雅的用法。popitem()稍微少见一点在Python 3.7之后它删除并返回字典的最后一个键值对。在低版本里它删除的是任意一个但3.7之后字典有序化了所以行为就稳定了。它主要用于某些需要逐对弹出的算法场景比如实现栈操作或逐对处理映射数据。清空整个字典用clear()。清空和把变量重新赋值为空字典在语义上不同一个是原地清掉一个是重新绑定一个新的空对象涉及外部引用的时候差异至关重要。3.4 键存在性检查in 其实比你想的更快很多人判断字典里有没有某个键时习惯遍历一遍或者转列表这是完全错误的做法。字典内部是基于哈希表实现的in判断的时间复杂度是平均 O(1)不管你字典里装了几万条还是几条判断速度基本恒定。判断键和判断值不一样这也是很多新手容易混淆的地方if 张三 in score: # True判断键 if 92 in score.values(): # 判断值是否出现在所有值中判断值要用.values()但这里要提醒你in score.values()是 O(n) 的因为值不是哈希索引的。3.5 一趟循环拿全键值遍历的正确方式遍历字典最常犯的错误是只遍历键然后每次在循环体里再去访问值。这在功能上没错但不够高效也不够优雅。正确的姿势是用items()for name, sc in score.items(): print(f{name}: {sc})如果你只想遍历所有键用for key in score而不是score.keys()——前者更简洁而且效果一样。如果你想遍历所有值再用score.values()。遍历的时候还有一个大坑不能在遍历字典的过程中直接修改字典的结构。比如一边for循环一边del删除某些键Python会直接抛RuntimeError: dictionary changed size during iteration。正确做法是先把需要删除的键收集到一个列表里循环结束后再统一删除或者用字典推导式重新生成一个新的字典。3.6 默认值处理setdefault 与 defaultdict我有一次写一个词频统计程序每读到一个词需要让该词的计数加一。最开始我是这样写的for word in words: if word in count: count[word] 1 else: count[word] 1功能没问题但每次要写这个if...else...总觉得不太利落。后来学会了setdefaultfor word in words: count[word] count.setdefault(word, 0) 1setdefault(key, default)的意思是如果键存在返回它的值如果键不存在把键设为default并返回default。一步完成取原值或初始化的动作。更好的做法是用标准库的collections.defaultdictfrom collections import defaultdict count defaultdict(int) for word in words: count[word] 1这里defaultdict(int)的意思是当访问一个不存在的键时自动调用int()生成默认值 0然后你可以直接对这个值做加法。词频统计瞬间变成了两行代码。defaultdict不止能配int任何可调用对象都可以比如list、set甚至你自己定义的工厂函数。3.7 字典解包与快速合并Python 3.9 之后的写法如果你还在用 Python 3.8有两个字典要合并最稳妥的方式是merged {**d1, **d2}这是字典解包操作把两个字典的所有键值对铺开放进一个新字典里。如果两边有相同的键后面的d2会覆盖d1的值。如果你升级到了 Python 3.9 及以上就可以直接用合并运算符merged d1 | d2也可以原地合并d1 | d2这一批新语法让字典合并变得非常直观写代码的心情都好不少。特别在意兼容性的话注意一下你项目里的Python版本再决定用哪种写法。4. 字典背后的哈希表以及为什么查找能这么快很多人学字典就停在会用层面但为什么快这个点恰恰决定了你是否能正确理解字典的一些限制和坑比如为什么键必须是不可变的为什么说in是 O(1)又为什么Python 3.7之后字典顺序稳定了。这些跟哈希表的实现原理全都相关。4.1 哈希函数到底管什么哈希表的核心是一个哈希函数。字典存储键值对时并不会把这个键值对放在一个线性序列里让查找时逐个对比键——那样就退化成列表了。它会先对键做一次哈希运算算出这个键的哈希值一个整数然后把这个整数映射到底层数组的一个位置键值对就直接存在那个位置上。以后再查这个键是否在字典里流程就是对键再做一次同样的哈希运算得到同样的整数然后直接到底层数组的那个位置去看那里有没有东西如果有再确认键是否完全相等。这个过程和字典里已有多少个键值对无关所以平均复杂度是 O(1)。这就解释了为什么要求键必须是不可变类型如果键本身是可变的比如列表它在存储后内容被改了再算一次哈希值就和存进去时不一样那就永远找不到了。这跟一个人藏了一个东西在藏的时候画了个记号结果东西还在他后来把记号擦掉重画了一个然后自己再按新记号去找肯定找不到。4.2 哈希冲突和性能退化哈希函数不是完美的不同键算出相同哈希值的概率虽然小但不是零。这就叫哈希冲突。Python的字典底层会用一种叫开放寻址的方式处理冲突如果计算出来的槽位已经被占了就按一套规则去找下一个空位。这个过程中查找效率会有所下降但平均下来依然很快。所以字典的性能在绝大多数业务场景下是拼接近 O(1)的。只有在一种极端情况下性能会退化你故意构造一堆哈希值相同的键。以前某些网络攻击方式就是通过构造这样的数据让服务端的哈希表退化到 O(n)从而把CPU时间耗光这种问题在Python中也存在。常规开发不用过度担心这个但要知道字典的 O(1) 是有前提的不是绝对真理。4.3 3.7 的顺序保障字典到底有序还是无序在Python 3.6及以前的版本字典的无序性是明确的虽然底层实现上3.6已经有了一些预排序的迹象但规范层面还没给出保证。Python 3.7开始官方明确承诺字典的键值对会保持你插入时的顺序。也就是说你遍历字典拿到的顺序就是你往里面添加数据的顺序。这个特性平时不起眼但如果你依赖按添加顺序输出那在3.7以上的解释器上是安全的行为。另外popitem()的行为也因此从随机弹变成弹最后一个所以实现类似 LIFO 的操作就不需要额外维护顺序了。如果你想按其他规则排序遍历字典比如按值大小排序需要借助sorted和一个 lambdafor name, sc in sorted(score.items(), keylambda item: item[1], reverseTrue): print(f{name}: {sc})这行代码是按值从高到低输出所有人。keylambda item: item[1]的意思是用来排序的参照物是每个键值对的第二个元素也就是值本身。5. 嵌套字典与实战场景你迟早要遇到这些字典简单的时候真的简单但一旦进入真实业务你面对的几乎都是嵌套结构也就是字典里面套字典甚至字典套列表、列表又套字典。这种结构支撑了大量真实系统的数据组织值得专门拆一下。5.1 用户数据、配置系统和JSON先看一个最常见的场景从接口或数据库里读一条用户信息往往长这样user { id: 1, name: 张三, profile: { age: 18, city: 北京, tags: [学生, 文学社成员] }, settings: { theme: dark, notifications: { email: True, sms: False } } }这种结构在Python里叫嵌套字典在读JSON响应时几乎天天见。访问嵌套路径里的某个值直接连续用方括号print(user[profile][city])但如果这个路径中间某一层不存在就会抛KeyError。比如一个新增字段只对部分用户存在访问user[settings][notifications][sms]可能中间漏一层。这种时候get()的链式调用就很有用email_enabled user.get(settings, {}).get(notifications, {}).get(email, False)get接上默认值不存在的路径降级为False程序不会崩行为也可控。5.2 用嵌套字典做分组统计统计场景中嵌套字典的价值很大。比如你有一批订单数据每条包含城市和金额你想按城市分组统计总金额orders [ {city: 北京, amount: 100}, {city: 上海, amount: 200}, {city: 北京, amount: 150}, {city: 广州, amount: 80}, ] city_total {} for order in orders: city order[city] city_total.setdefault(city, 0) city_total[city] order[amount]结果就是{北京: 250, 上海: 200, 广州: 80}如果想看到更细的粒度比如每个城市里按商品类别再分组就可以在值里再放一个字典。5.3 用嵌套字典配置项目配置管理是另一个高频场景。项目里不同模块各有各的配置项全部拍平放在一层里不好维护用嵌套字典天然形成层级config { database: { host: localhost, port: 3306, user: root, password: ***, }, redis: { host: localhost, port: 6379, }, log: { level: DEBUG, file: app.log, } }读取的时候层级清晰维护的时候也不容易改错别的模块的配置。5.4 多层嵌套的访问层层防御多层嵌套结构在访问时有个通用原则能收窄访问范围就收窄。如果你一个业务逻辑只需要用到数整个嵌套结构里的一个叶子值用一条长长的方括号链去取代码可读性很差也容易被中间层None卡死。有一种工具写法是在拿到接口数据后用循环或显式逐级get但我更喜欢写一个小函数来提取路径值尤其是当路径特别深的时候。这种写法可以封装得非常简洁虽然现在也有一些第三方库做类似的路径访问但在不引入额外依赖的前提下手写一个safe_get完全可以满足需求。常规项目里不到万不得已不建议搞得太花哨但一个大原则值得记下来嵌套结构访问的代码宁可多写几行做防御也不要为了短省掉那几条get。生产环境里报一千个KeyError和写十行防御代码后者划算太多。6. 字典在数据清洗与映射场景中的实用技巧字典除了用来存数据还有一个非常常见的用途是替换映射。这类需求在数据处理场景中特别多比如把数据库里存的编号翻译成可读文字或者把某种原始字段值标准化。这种用法非常典型单独拿出来说说。6.1 映射表做值替换比如你有一个人工录入的性别字段可能存了多种写法你想统一成标准值gender_map { 男: M, male: M, M: M, 女: F, female: F, F: F, } raw_values [男, female, M, 女, unknown] normalized [gender_map.get(v, UNKNOWN) for v in raw_values]一段代码就把脏数据洗干净了。get加默认值的好处也体现出来了没匹配上的不会崩直接给你一个兜底值方便后续排查哪些值不在映射范围里。6.2 用字典反转实现反向查找有时候你需要反过来查拿值找键。比如状态码查状态描述是正着来的但你想根据描述反推有哪些状态码对应这个描述简单场景下可以反转字典status_map {200: OK, 404: Not Found, 500: Internal Server Error} desc_to_code {desc: code for code, desc in status_map.items()}这里有一个前提反转后的字典键必须唯一。如果多个原键映射到同一个值反转后会丢失部分信息因为后写入的键值对会覆盖先前的。遇到这种场景不要用推导式直接反转而是应该把值收集成列表。6.3 用 collections.Counter 做频率统计前面讲过defaultdict做词频统计其实如果只是统计频率标准库里还有一个更专用的工具叫Counterfrom collections import Counter words [apple, banana, apple, orange, banana, apple] counter Counter(words) print(counter) # Counter({apple: 3, banana: 2, orange: 1})Counter是dict的子类所以字典的所有通用操作它都支持。它额外提供了most_common(n)方法可以一次性拿到频率最高的前几个元素。这种附带排序的次数统计在排行榜、Top N 分析、词频统计中使用率极高值得每个写Python的人熟悉。6.4 字典完成数据去重、交集、差集字典的键天然唯一这层特性可以用来去重但这种需求通常用set就够。真正有意思的是两个字典之间的集合运算。两个字典的键求交集common_keys d1.keys() d2.keys()两个字典的键求差集only_in_d1 d1.keys() - d2.keys()这是Python字典键视图一个不太起眼却很强大的特性。dict_keys视图可以直接参与集合运算不需要转成set。实际中用这种写法可以快速比较两份配置之间有哪些新增、删除了哪些配置项或者找出一批ID里面哪些已经存在于另一个系统返回的字典里。7. 常见问题与避坑指南全是实战总结这一节让我集中写一些日常开发中容易踩的坑。很多坑我都是自己踩过或者看同事踩过之后总结下来的希望能帮你绕过去。7.1 试图给同一个字典用两个变量名分别操作结果互相影响字典是可变对象如果你写了d2 d1这两个变量指向的是同一个字典对象不是复制了一份。你改了d2里的值d1也会跟着变。这是引用传递的特性。如果你需要一份独立的副本要用拷贝操作。浅拷贝d2 d1.copy()浅拷贝能独立修改最外层键值对但如果字典里有嵌套的可变对象比如列表或另一个字典那嵌套层依然是共享的。需要完全独立时就上深拷贝import copy d2 copy.deepcopy(d1)这个差别在配置对象、模板数据、状态快照等场景里尤其致命。我用深拷贝踩过最大的坑是从全局配置复制了一份做临时修改结果深拷贝没加上改着改着把全局配置的数据也改了线上报了一整天的错。7.2 遍历时修改字典结构导致 RuntimeError下面是错误示范for name in score: if score[name] 90: del score[name]这样跑起来大概率抛RuntimeError: dictionary changed size during iteration。正确做法是收集要删的键循环外统一删to_remove [name for name, sc in score.items() if sc 90] for name in to_remove: del score[name]也可以直接生成一个过滤后的新字典不修改原字典。7.3fromkeys的可变默认值陷阱前面提到过dict.fromkeys(keys, [])创建的字典里所有键共享同一个列表。这种陷阱尤其常出现在初始化一个分组容器的场景。比如groups dict.fromkeys([A, B, C], []) groups[A].append(1) # 结果groups[B] 和 groups[C] 也变成了 [1]要避坑就用字典推导式为每个键创建独立列表groups {key: [] for key in [A, B, C]}7.4 可变对象的引用成为默认值这个Bug能隐藏很久下面这段代码很多人刚开始学函数时都写过def add_to_dict(key, value, target{}): target[key] value return targetPython函数的默认参数值是在函数定义时求值一次并复用的所以target{}的默认值只创建一次多次调用这个函数默认目标字典是同一个。连续调用几次你会发现字典里的数据越积越多。避免方式很简单默认值用None函数体内再初始化def add_to_dict(key, value, targetNone): if target is None: target {} target[key] value return target7.5 取完值之后忘了键可能不存在KeyError频繁报新手期人人都会经历。关键是养成一个条件反射如果键是不是一定存在我不能确定就优先用get或setdefault。写代码时多问自己一句这个键有没有可能不在能省掉后面很多调试时间。7.6 以为字典的输出顺序是随机的拖到嵌套结构里以为数据乱序Python 3.7之后字典已经保序了插入顺序就是遍历顺序。如果你发现输出顺序不正确多数情况是你一开始插入的顺序就不是你预想的那个顺序。想按别的规则排序就显式用sorted不要指望字典会自己聪明地排序。7.7 忽略键的唯一性导致更新意外覆盖字典的键是唯一的看起来是常识但实际中确实会发生从多个来源收集数据某两个来源用了同一个键后者直接覆盖前者。如果你需要保留所有值键就要设计成唯一或者值设计成列表。这个取舍设计阶段就要想清楚不要留到线上才处理那个怎么数据少了一条的问题。8. 几个组合技巧能显著提升你的字典使用效率前面覆盖了大部分基础操作最后再聊几个组合用法。这些技巧单独看不显眼结合起来能大幅减少很多场景的代码量。8.1 用collections.ChainMap把多个字典视为一个整体有时你有几个偏好设置局部优先其次全局最后默认值。把它们合并成一个字典是一种办法但每次合并都要复制数据。ChainMap则提供了一种不复制、按顺序查找的方案from collections import ChainMap defaults {theme: light, lang: zh-CN, font_size: 14} user_settings {theme: dark} settings ChainMap(user_settings, defaults) print(settings[theme]) # dark print(settings[lang]) # zh-CNChainMap在查找时从左到右依次找找到为止。需要多层配置合并时比手动update和一整串逻辑判断轻巧许多。8.2 字典配合operator.itemgetter做多级排序如果你有多个字典组成的列表想按某个字段排序lambda写法是常规操作但更简洁的可以组合itemgetterfrom operator import itemgetter users [ {name: 张三, age: 18, city: 北京}, {name: 李四, age: 22, city: 上海}, {name: 王五, age: 16, city: 广州}, ] sorted_by_age sorted(users, keyitemgetter(age))要按年龄和城市两级排序时itemgetter(age, city)一次搞定不用嵌套多层sorted。8.3 用字典做缓存让重复计算的代码提速字典的 O(1) 查询非常适合做缓存当然常规缓存有第三方库的方案但纯手写一个轻量缓存也很常见cache {} def get_fibonacci(n): if n in cache: return cache[n] if n 1: result n else: result get_fibonacci(n - 1) get_fibonacci(n - 2) cache[n] result return result这种写法在递归算法里效果显著能去掉大量的重复计算。面试时很爱考的带记忆化的递归本质就是字典缓存。8.4 字典在解构多条记录中的妙用循环读取多条字典记录用解构能省掉不少取值变量for name, profile in user_dict.items(): age profile.get(age, 0) city profile.get(city, 未知) print(f{name} 住在 {city}年龄 {age})先遍历items()拿到键值对再对值做统一处理这是处理嵌套字典的主流思路。实际开发中配合防御性get基本可以覆盖绝大部分业务数据解析需求。8.5 字典路由替代一长串if...elif...当你需要根据某个字段值执行不同函数时比起写一长串分支判断直接用一个字典做路由分发表会更清晰def handle_create(data): pass def handle_delete(data): pass def handle_update(data): pass handlers { create: handle_create, delete: handle_delete, update: handle_update, } def dispatch(action, data): handler handlers.get(action) if handler is None: raise ValueError(f不支持的 action: {action}) return handler(data)字典的值不局限于数据也可以是函数对象。这种表驱动的设计在命令分发、事件处理、菜单选择等场景里非常常见代码结构也比一长串条件分支清晰得多。9. 写在最后的一些扩展方向我用了大量篇幅介绍字典本身但真要写出优雅的Python代码字典往往还要和标准库里的其他工具结合使用。dataclasses适合定义一张结构固定的数据表NamedTuple适合轻量化记录TypedDict可以给字典加上类型约束让静态检查器帮你发现拼写错误这些都是字典的近亲在各自场景下各有优势。如果你现在还在用列表到处模拟映射关系我建议从现在开始把那些代码改成字典感受一下按名字取数据这种思维的转变。字典不仅仅是Python的一种数据结构更是一种以键直达值的编程模型。一旦习惯了这种模型你再看很多业务需求脑子里自动就会浮现出这个用字典怎么组织的方案。写到最后分享一个我自己的体会。学Python有一段时间之后你会发现自己写代码的分水岭往往不在于语法熟练度而在于脑中有没有一套数据结构思维。列表是顺序思维的产物字典是映射思维的产物。凡是涉及查找、归类、映射、去重、关联关系的场景第一反应应该是字典。等你能熟练做到这一点Python的程序设计水平基本也就跨上一个台阶了。