字符串、dict、tuple — 三种容器的分工
字符串、dict、tuple — 三种容器的分工
这一章讲三种数据结构,但只为一个问题服务:一个东西该放进哪种容器? 原书的答案很硬:字符串只存文本;dict 管「键 → 值」的查找;tuple 是「几个值的只读组合」。 判断标准不是「哪个功能多」,而是「这个东西在问题里扮演什么角色」。
1. 这一章讲什么
上一章(list)解决「一组同类对象」;本章解决剩下的三种日常容器,原书各给了一句话的定位:
- 字符串(text):代码里的文本模块,两头带引号;1
- dict(字典):按名字存值,运行时可以动态地加条目——普通变量写代码时就定死了名字,dict 的键可以到时候才有;2
- tuple(元组):「几个值的组合」,和列表几乎一样,但不能改。3
容器 编制 什么时候用
str 一段文本 只有真的是文本时(人读的话)
dict 名字 → 值 键名运行时才确定;按名字查,不按位置
tuple 只读的组合 函数一次吐回好几个值;不该再拆开重组
图说:三种容器 + 上一章的 list,构成 Python 日常数据的四种编制。2. 字符串:只存文本,以及 f-string 这个主力技能
2.1 创建与拼接
解决什么问题: 程序要处理人读的文本:名字、句子、文件内容。
怎么做: 单双引号随意("House" 和 'House' 等价;原书习惯双引号,REPL 显示时偏好单引号)。
两个基础操作:加号拼接、split 按分隔符(比如逗号、空格)拆开:
"Hello " + "World" # 'Hello World'
"1,2,3,4".split(",") # ['1', '2', '3', '4'] —— 读 CSV 的基本动作
"Hello World".split(" ") # ['Hello', 'World']顺带一提:"Hello " * 3 是 'Hello Hello Hello '——字符串可以乘,书里评价「好玩,但没什么用」。4
字符串 ≈ 只读列表(第 04 章的桥):能遍历 for letter in "Hello"、能按下标取,但改单个字符会报 TypeError。
所以列表那章的遍历直觉直接平移过来。5
2.2 f-string:把变量的值织进文本
解决什么问题: 生成「含变量内容」的句子,比如 I have 10 Apple in stock.,其中数量和品名来自变量。
怎么做(主线例子): 字符串开头加 f,要插入的变量名用大括号包起来:6
fruit_name = "Apple"
quantity = 10
f"I have {quantity} {fruit_name} in stock." # 'I have 10 Apple in stock.'走一遍:f 告诉 Python「这个串里有大括号,大括号里的是变量名,把它现在的值织进来」;
数量和名字被替换成 10 和 Apple,其余字面原样保留。数字、列表都行——「Python 里任何对象都能转成字符串」,所以任何对象都能进 f-string。7
为什么有效、边界在哪: 它是 Python 3.8 引入的写法,也是今天写 Python 的默认姿势
(更早的 % 格式化和 .format() 照旧能跑,但新代码不用学它们)。
边界:大括号里的表达式别写复杂——f"{...}" 里塞运算,可读性立刻塌方;复杂的先算好再织。
2.3 特殊字符与三种变体字符串
解决什么问题: 换行、引号、反斜杠这类字符有「代码含义」,直接写进字符串会被解释器抢去当指令 (书里把它类比成 SQL 注入:特殊字符混进字符串、被当成代码执行——凡是读用户输入,都要确保内容只被当字符串)。 最简单的绕行:串里要用单引号就用双引号包,反之亦然。8
三种变体,各管一种「普通字符串不舒服」的场景:9
| 变体 | 写法 | 管什么 |
|---|---|---|
| f-string | f"...{name}..." | 织入变量值 |
| 多行字符串 | """..."""(三引号) | 跨多行的文本 |
| 原始字符串 | r"Here is a \n" | 反斜杠不转义,\n 就显示成两个字符 |
2.4 头号反模式:拿字符串存结构化信息
书里把这个警告放进了加框的「Using of Strings」:
字符串只该用来存文本,别的不存。 常见反模式是拿字符串存某种「结构化对象」式的信息——非常容易出错。 要存结构化信息,用类(第 11 章);存简单选项,用 enum(第 06 章);存数据文件,用 XML/JSON 这类为结构化数据设计的格式。10
第 03 章那句「别碰字符串,除非万不得已」在这里落地了:字符串没有结构,解析字符串全靠约定,而约定会 silent 地崩。
作者给的收尾规矩同样硬:「字符串要在读进来的当时就处理好;其余代码里不该有任何字符串逻辑。永远不。」
(例:读 CSV 时 "42" 当场 int() 成 42,之后的世界里没有字符串。)
一个顺带的小勘误:书里示例用了 "Hello".contains("l")——contains 不是 Python 字符串的方法,
正确写法是 "l" in "Hello"(返回 True)。照书敲会报 AttributeError。4
3. dict:按名字存取,运行时动态生长
3.1 创建、存取、遍历
解决什么问题: 普通变量的名字在写代码时就定死;如果键名要到运行时才知道(比如读进来的文件), 就需要一个「名字也能动态」的容器。
怎么做(主线例子): 一本菜谱,菜名 → 原料清单:2
recipes = {} # 空 dict
recipes["Pizza"] = ["Dough", "tomatoes", "cheese"] # 键 "Pizza" 是运行时写进去的
recipes
# {'Pizza': ['Dough', 'tomatoes', 'cheese']}
recipes["Pasta"] = ["Penne", "tomatoes"]
for name in recipes: # 遍历键
print(name) # Pizza / Pasta
for ingredients in recipes.values(): # 直接遍历值
print(ingredients)每个条目是**键(key)→ 值(value)**的一对;键几乎可以是任何不可变对象(不限于字符串,整数也行:{1: "one"})。
给不存在的键赋值就是新增条目,这给 dict 一个列表没有的能力:按内容生长。
计数模式: 统计一段文本里每个词出现几次——「这个代码片段值得记住」:
text = "This is a test. This is only a test."
word_counts = {}
for word in text.split():
if word in word_counts:
word_counts[word] += 1 # 见过:加一
else:
word_counts[word] = 1 # 没见过:建条目
# {'This': 2, 'is': 2, 'a': 2, 'test.': 2, 'only': 1}if word in word_counts 查键在不在——平均查找时间与 dict 大小无关(背后是哈希表实现——哈希是一种把任意数据快速映成固定编号的技巧;
这是「查找」这个词在数据结构里的技术含义:按名字找到值,且不论装了多少都几乎一样快)。
书里的俏皮话:「有个笑话,说 dict 是所有 LeetCode(编程题库)题的解——不完全对,但足以说明它多重要。」11
为什么有效、边界在哪: dict 把「按名字找」从 if-else 长链里解放出来;
边界:遍历顺序、键必须不可变、以及 word_counts[word] 查不存在的键会报错(要用 in 先探,或第 06 章的 get——书里没讲,补充:.get(key, 默认) 取不到时返回默认,不报错)。
3.2 dict vs 变量 vs 类
dict 和普通变量都能「名字 → 值」,差别是名字的确定性:写代码时就知的固定几个字段,
用变量或类更直白(.name 永远比 ["name"] 可读,还记得第 02 章吗);
运行时才能数出来的条目集合,才是 dict 的主场。12
4. tuple:只读组合,为「一次返回多个值」而生
4.1 基本行为
解决什么问题: 一个函数想同时吐回两个结果——商和余数、面积和周长。
怎么做(主线例子): 内建函数 divmod 返回的就是 tuple:13
divmod(10, 3) # (3, 1) —— 商 3,余 1;这个括号+逗号就是 tuple 字面量
quotient, rest = divmod(10, 3) # 解包:左边几个名字,右边几项,一一对应
quotient # 3解包(unpacking) 是 tuple 的灵魂操作:把组合按位置拆开,每个值落进一个名字。
自定义函数一样用——return area, perimeter 返回的其实是 (area, perimeter) 这个 tuple,
调用处 area, perimeter = rectangle(3, 4) 当场拆开。tuple 括号可以省略,逗号才是本体。14
只读是硬约束:a[0] = 4 直接 TypeError: 'tuple' object does not support item assignment——
和第 03 章「不可变」的契约完全同构:调用者拿到它,就确定它不会被半路改掉。15
4.2 与 list 的分工
书里把分工说得直白:list 装一组性质相同的对象(为了遍历它们);tuple 可以装不同的对象,但—— 「我建议你别到处传 tuple。要装不同的对象,就该建一个类。」 所以作者本人「几乎不用 tuple」, 留给它的正当场景基本只剩「函数返回多个值」这一处。16
两个实用提醒:
- tuple 别太大:超过两三个元素就容易丢线索;「返回 tuple 往往说明函数干了太多事」——拿不准就返回一个值(或一个类)。
- 第 04 章
zip的产物是 tuple 列表:list(zip([1,2,3],[4,5,6]))→[(1,4),(2,5),(3,6)]。
5. 作者的判断与证据
书里给了证据的: 所有代码示例可直接运行;dict 计数模式是完整可复用的模板;11
TypeError 报错信息展示了只读语义。15
作者的判断(立场):
- 「字符串只存文本、其余代码零字符串逻辑」——强度很高,方向正确;真实代码里字符串工具函数(清洗、格式化)不可避免,他的意思更接近「业务逻辑别建立在解析字符串上」;
- 「别传 tuple,要装异质对象就用类」——和他「别用 []、别切片」一脉相承的强默认;
- dict「查找平均常数时间」是机制事实,可直接信任。11
判断(我们的,不是书里的): 这一章三个容器对应三种「信息形状」——文本(给人读)、映射(按名查)、记录(成组返回)。 作者没说的第四 shapes 是记录该升级成类:当 tuple/dict 开始被传来传去、字段名靠记,就是升级信号。 他的「几乎不用 tuple」对初学者是安全的默认,代价是错过解包这个 Python 里非常好用的语法糖——解包值得学,传大 tuple 值得戒。 如果错,会错在: 数据科学代码里,一行
name, value = pair式的轻量解包无处不在, 「别用 tuple」如果理解成「别解包」,就把孩子和水一起倒了——他否定的只是「把 tuple 当数据结构传来传去」。
6. 边界与局限
- 字符串方法只示范了 split/拼接,
strip、join、startswith等常用件没提(第 14 章 CSV 例子里倒是用了strip); - dict 的
get、items()、字典推导式没讲——for k, v in d.items()是日常高频,读者需自查; - 书里
contains示例是错的(已在本章第 2.4 节勘误); - tuple 的具名替代
NamedTuple/dataclass(第 11 章的@dataclass才是作者的答案)在本章没点名衔接。
7. 可带走的
本章主线例子,一行写完:
字符串 f"I have {quantity} {fruit_name}" 织值入文;
dict word_counts[word] += 1(不存在则建条目)统计词频;
tuple quotient, rest = divmod(10, 3) 一次返回、当场解包。61113
- 字符串只存文本;拿它存结构化信息是头号反模式——该用类、enum 或 JSON/XML。
- f-string 是拼文本的默认写法:
f"...{变量}...";读入的字符串数据要当场转成正确类型。 - 特殊字符三变体:
f""织值、"""..."""多行、r""原样反斜杠。 - dict = 运行时才确定键名的「名字→值」;计数模式(
if in: +=1 else: =1)背下来。 - dict 查找平均与大小无关——可以大胆用在性能关键处。
- tuple = 只读组合;就留给「函数返回多个值 + 解包」,别当数据结构传来传去。
- 容器选择问一句:「这个东西在问题里扮演什么角色?」角色决定编制,不是功能多少。
8. 原文地图
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 字符串定义、引号 | Strings | text/14-fm-strings.txt:15(搜「quotation marks」) |
| f-string 主线例子 | Strings | text/14-fm-strings.txt:26(搜「My name is」) · text/14-fm-strings.txt:74(搜「I have」) |
| 三种变体(f/多行/原始) | Strings | text/14-fm-strings.txt:63(搜「Various Types」) · text/14-fm-strings.txt:79(搜「multiline」) · text/14-fm-strings.txt:85(搜「raw string」) |
| 特殊字符与 SQL 注入类比 | Strings | text/14-fm-strings.txt:52(搜「SQL injection」) |
| 「字符串只存文本」反模式警告 | Strings | text/14-fm-strings.txt:34(搜「Using of Strings」) |
| 字符串操作与 contains 勘误 | Strings | text/15-fm-dicts.txt:1(搜「String Operations」) · text/15-fm-dicts.txt:35(搜「contains」) |
| dict 定义与动态键名 | Dicts | text/15-fm-dicts.txt:50(搜「dynamically」) |
| dict 遍历 keys/values | Dicts | text/15-fm-dicts.txt:66(搜「recipes[”Pasta”]」) · text/15-fm-dicts.txt:82(搜「values()」) |
| 词频计数模式 | Dicts | text/15-fm-dicts.txt:93(搜「frequency」)与 text/16-fm-tuples.txt:1(搜「word_counts」) |
| 查找常数时间、LeetCode 笑话 | Dicts | text/16-fm-tuples.txt:16(搜「constant」) |
| tuple 定义与 divmod | Tuples | text/16-fm-tuples.txt:24(搜「inconspicuous」) · text/16-fm-tuples.txt:43(搜「divmod」) |
| 解包与自定义返回 | Tuples | text/16-fm-tuples.txt:52(搜「Unpacking」) · text/16-fm-tuples.txt:62(搜「rectangle」) |
| tuple 别太大、作者几乎不用 | Tuples | text/16-fm-tuples.txt:72(搜「too large」) · text/16-fm-tuples.txt:97(搜「hardly ever use」) |
| list vs tuple 分工 | Tuples | text/16-fm-tuples.txt:76(搜「Tuples vs. Lists」) |
Footnotes
-
出处:「Strings」第 14–17 段(
text/14-fm-strings.txt:14,搜「text modules」)。 ↩ -
出处:「Dicts」第 50–57 段(
text/15-fm-dicts.txt:50,搜「dynamically」)。 ↩ ↩2 -
出处:「Tuples」第 24–26 段(
text/16-fm-tuples.txt:24,搜「inconspicuous」)。 ↩ -
出处:「Dicts」文件开头「String Operations」节(
text/15-fm-dicts.txt:6,搜「concatenate」;第 11 段,搜「not particularly useful」;第 34 段,搜「contains」)。勘误:contains不是 Python 字符串方法,正确写法是"l" in "Hello"。 ↩ ↩2 -
出处:「Lists」第 251–260 段(「Strings vs. Lists」框,
text/13-fm-lists.txt:252,搜「list of characters」)。 ↩ -
出处:「Strings」第 21–32 段(
text/14-fm-strings.txt:26,搜「My name is」)。 ↩ ↩2 -
出处:「Strings」第 41–45 段(
text/14-fm-strings.txt:41,搜「can be converted into」)。 ↩ -
出处:「Strings」第 48–61 段(
text/14-fm-strings.txt:52,搜「SQL injection」;第 58 段,搜「vice versa」)。 ↩ -
出处:「Strings」第 63–89 段(
text/14-fm-strings.txt:79,搜「multiline string」;第 86 段,搜「raw string」)。 ↩ -
出处:「Strings」第 35–39 段(加框「Using of Strings」,
text/14-fm-strings.txt:35,搜「antipattern」)。 ↩ -
出处:「Dicts」第 91–98 段(
text/15-fm-dicts.txt:93,搜「frequency」)与「Tuples」文件开头(text/16-fm-tuples.txt:16,搜「constant」;第 17 段,搜「LeetCode」)。 ↩ ↩2 ↩3 ↩4 -
出处:「Dicts」第 88–90 段(
text/15-fm-dicts.txt:89,搜「enormously useful tool」)。 ↩ -
出处:「Tuples」第 41–57 段(
text/16-fm-tuples.txt:43,搜「divmod」)。 ↩ ↩2 -
出处:「Tuples」第 59–71 段(
text/16-fm-tuples.txt:62,搜「rectangle」;「括号可省」见第 61、70 段)。 ↩ -
出处:「Tuples」第 34–39 段(
text/16-fm-tuples.txt:39,搜「does not support item assignment」)。 ↩ ↩2 -
出处:「Tuples」第 94–97 段(
text/16-fm-tuples.txt:97,搜「hardly ever use」)。 ↩