计数器 — 数东西这件事的最终形态
计数器 — 数东西这件事的最终形态
这一章讲一件事: 数一堆东西里每种各有多少个——这个任务你已经能用 字典写了,但 Counter(计数器)让你一行写完,还能顺手按多少排好序。 它是 collections 三件套的最后一件,也是这条演进线的终点: 普通字典 → defaultdict → Counter,样板代码一段一段被收进工具里,你的代码 只剩「要数什么」本身。
1. 这一章讲什么、在全书链条里的位置
第四篇的三件套,前两件各修一个结构的一种脾气: namedtuple 修元组的「没名字」,defaultdict 修字典的「缺键报错」。 Counter 走的更远一步——它为「计数」这一个任务整个定制: 它本质上是一个字典(键是元素、值是次数),但造它的时候直接把数据喂进去, 它自己数完1。这是全书的最后一个机制,也是数据篇的收官。
2. 顶层全景:数同一篮水果,三级台阶
同一篮水果:['apple', 'banana', 'apple', 'orange', 'banana', 'banana']
正确答案:banana 3 个、apple 2 个、orange 1 个
第 ① 级 普通字典:循环 + 「在不在」判断 + 在则 +1 不在则建 ── 5 行
第 ② 级 defaultdict(int):循环 + 直接 += 1 ───────────────── 3 行
第 ③ 级 Counter:一行 Counter(数据) ──────────────────────── 1 行
图说:三级台阶走完整本书 collections 篇的主线——把重复的样板 progressively 收进工具。主线例子是这篮 6 个水果,走全程,每个数都出自原书的实际运行2。
3. 核心原理:两个机制,各走一步主线例子
3.1 前两级的写法,本章各给一段
普通字典版(第 ① 级):循环每个水果,先问在不在——在,次数加 1; 不在,建这个键、次数写成 1。5 行,其中 3 行是防守2。
defaultdict 版(第 ② 级):防守交给默认值,循环体只剩
fruit_count[fruit] += 1——查不到就先放 0 再加。3 行2。
这两级就是第 14、17 章的内容,原书在这里把它们并排印出,让台阶的落差自己说话。
3.2 Counter 版:连循环都省了
第 ③ 级长这样:
from collections import Counter
basket = ['apple', 'banana', 'apple', 'orange', 'banana', 'banana']
fruit_count = Counter(basket)就这一行。Counter(basket) 做的事:把篮子整个接过去,自己遍历一遍,
每个元素见过几次记几次,直接给你一个数好的字典。
打印出来是 Counter({'banana': 3, 'apple': 2, 'orange': 1})——
注意 banana 排在最前:Counter 顺手按次数从多到少排了序2。
为什么有效: 数数这个任务的三件套——遍历、判断、累加——前两件被 Counter 的构造动作整个吞掉。你不再告诉它「怎么数」,只告诉它「数什么」。
3.3 most_common():按频次排队取前几名
数完不算完,实际任务几乎总要追问一句「最多的是谁?前几个是谁?」
Counter 的 most_common() 方法管这个。书里的水果店例子:一天卖掉的流水是
apple×4、banana×3、orange×2(9 笔)。两行问答:
item_count.most_common(1) # → [('apple', 4)] 卖得最好的一种
item_count.most_common(2) # → [('apple', 4), ('banana', 3)] 前两名返回值是一列表,每个元素是 (东西, 次数) 的小元组,已经按次数排好队。
数字母的例子更能看出它和字符串功夫的配合:
想数 Python is a powerful programming language 里每个字母出现几次,
先 .replace(' ', '') 去掉空格、.lower() 全转小写,再交给 Counter,
most_common(5) 直接报前五名:a 和 g 各 4 次,p、o、n 各 3 次3。
去空格和转小写是计数前的「清洗」——这两步仍是你的活,Counter 只管数。
3.4 词频练习走一遍:从一段原文到一张前 10 榜
练习给了一段关于 Python 本身的四段介绍文字(从「high-level, general-purpose」 讲到「machine learning community」),要求忽略大小写、统计每个词出现几次、 打印最常用的前 10 个4。
word_count = Counter(paragraph.lower().split())
for word, count in word_count.most_common(10):
print(f'{word}: {count}')三步行:.lower() 全转小写(于是 'Python' 和 'python' 算同一个词)、
.split() 按空格切成词列表、Counter 接手数完并按多少排好。
榜单一出,第一名的数相当醒目:python: 9——这段文字里 'python' 出现了 9 次;
the 和 in 各 6 次;a、programming、and、as、released 各 4 次5。
(这正是计数前「什么算一个词」的默认口径:按空格切,所以带句点的 'language.'
和不带句点的是两个词——见第 5 节的边界提醒。)
数字版练习同理:13 个数的列表 [1, 3, 2, 2, 3, 3, 4, 5, 1, 2, 2, 4, 1],
Counter(numbers).most_common(3) 直接给出前三名:2 出现 4 次,
1 和 3 各 3 次——[(2, 4), (1, 3), (3, 3)]5。
4. 作者的判断与证据
作者明说的: Counter 会自动数出每个元素出现的次数、不需要写循环,
代码更干净、写得更快;并且它是「a subclass of dict」——字典的直系子孙,
keys 是元素、values 是次数,most_common() 返回按频次排好序的列表6。
证据: 本章证据就是三级台阶的并排演示(同一篮水果,三种写法)和两个 可运行的小案例(水果店流水、字母频次),数字全部出自真实运行。 章末案例把尺度放大到整本书:把莎士比亚《罗密欧与朱丽叶》的全文喂给 Counter,统计词频——并指了一条获取全文的公开来源(MIT 的莎士比亚站点), 把解法留白4。
我们的解读(不是书里的): 三件套到这里收齐,演进线的意义超出「少写代码」: 每上一级,你的代码就更接近你要问的问题本身。「Counter(文本) 的 most_common(10)」几乎就是把「这段文字最常用的 10 个词是什么」这句中文 直译成了 Python。
判断(我们的,不是书里的): 词频统计是 Counter 最能打的场景, 而「数之前先洗」是这个场景的隐藏门槛——本章所有数字例子前面都有 lower()、replace()、split() 这三板斧。新手真正的障碍不在 Counter 的 用法,而在意识到「什么算是一个词」是个要自己先回答的问题。 如果错,会错在: 如果数据已经干净(比如程序内部计数、投票记录), 清洗这一步根本不存在,那「隐藏门槛」就只是个比喻;反过来,处理自然语言 时这一步的讲究(标点、大小写、分词)比 Counter 本身深得多,本章没有展开。
5. 边界与局限
计数前的那半步仍是你的活。 Counter 只「数」,不「洗」:原书数单词的
练习里,paragraph.lower().split()(转小写再按空格切开)发生在喂给 Counter
之前;数字母的例子也要先去空格、转小写3。大小写、标点、分隔方式,
这些决定「什么算是一个词」的判断,Counter 一概不做。
它只接受可哈希的东西当元素。 键是元素本身,所以元素必须可哈希—— 列表当元素会直接 TypeError(这条只出现在本章给 AI 的提问清单里,正文没展开)7。 可哈希的大白话:能被字典当键用的东西(字符串、数字、元组可以;列表、字典不行)。
「次数」默认是整数,出现负数或不按整数加时怎么办,正文没讲。
官方文档里有 Counter 之间做加减、处理缺键的行为说明,原书把链接放在章末,
正文只教了构造和 most_common7。还有两个正文没提、清单里点到的口子:
most_common() 不传参数时给出全部元素按频次排好的完整榜,不只前几名;
两个 Counter 之间可以直接加减——今天卖的水果和昨天卖的相减,
差值就是每种水果销量的增减(这是官方文档的能力,原书只把它放进了
给 AI 的提问清单)7。
词频练习的一个小口径提醒: 练习里 paragraph.lower().split() 按空格分词,
所以 language.(带句点)和 language 会被当成两个词。统计结果的精确性
取决于这一步的切法——这是所有词频工具的共性,不是 Counter 的毛病。
6. 可带走的
- 数东西三级台阶:普通字典(循环+判断)→ defaultdict(只留循环)→ Counter(连循环都省);本章是终点,三件套主线在此收齐;
- 一行构造:
Counter(任意可遍历的数据);它本质是字典,键是元素、值是次数; most_common(n)直接给出前 n 名,排好序的(元素, 次数)列表; 不传 n 给全部;- 数之前想清楚「什么算一个」:去空格、转小写、按什么切开——清洗是你 的责任,Counter 只管数;
- 元素必须可哈希(能当字典键);列表、字典不能当元素;
- 想看「榜样的全貌」就调
most_common()不带参数——全部元素按频次排好, 不少不漏;两批数据各有一个 Counter 时,直接相减看增减; - 全书的取景框到这里为止:理论(是什么)→ 流程控制(怎么走)→ 函数(怎么复用) → 数据结构(怎么装)→ collections 三件套(怎么少写样板)。下一本书再往后走。
这一章短而收束:三级台阶、一个方法(most_common)、一个留白的大作业—— 三件套至此讲完,数东西的整条演进线在你手里合上。
7. 原文地图
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| Counter 的定位(接在 defaultdict 之后) | SECTION IV: Data Collections | text/25-fm-section-iv-data-collections.txt:517(搜「designed specifically for counting」) |
| 水果计数:普通字典版 | SECTION IV: Data Collections | text/25-fm-section-iv-data-collections.txt:527(搜「fruit count exercise」) · :530(搜「basket」) · :540(搜「'banana': 3」) |
| 水果计数:defaultdict 版 | SECTION IV: Data Collections | text/25-fm-section-iv-data-collections.txt:542(搜「save some effort」) · :554(搜「'banana': 3」) |
| 水果计数:Counter 版与「不需要循环」 | SECTION IV: Data Collections | text/25-fm-section-iv-data-collections.txt:557(搜「counts the occurrences」) · :563(搜「Counter(basket)」) · :567(搜「'banana': 3」) |
| Counter 是 dict 的子类、most_common 说明 | SECTION IV: Data Collections | text/25-fm-section-iv-data-collections.txt:572(搜「subclass of dict」) · :575(搜「most_common」) |
| 水果店流水案例(前 1、前 2 名) | SECTION IV: Data Collections | text/25-fm-section-iv-data-collections.txt:579(搜「store sells fruits」) · :594(搜「most_common(1)」) · :605(搜「Top 2」) |
| 数字母案例(清洗 + most_common(5)) | SECTION IV: Data Collections | text/25-fm-section-iv-data-collections.txt:610(搜「powerful programming language」) · :613(搜「replace」) · :617(搜「Top 5」) |
| 词频练习(top 10)与数字练习(top 3) | SECTION IV: Data Collections | text/25-fm-section-iv-data-collections.txt:664(搜「paragraph.lower().split()」) · :669(搜「python: 9」) · :695(搜「(2, 4)」) |
| Romeo and Juliet 案例(留白的终章作业) | SECTION IV: Data Collections | text/25-fm-section-iv-data-collections.txt:697(搜「ROMEO AND JULIET」) · :706(搜「shakespeare.mit.edu」) · :707(搜「not provided」) |
| 可哈希等边界(仅提问清单) | SECTION IV: Data Collections | text/25-fm-section-iv-data-collections.txt:730(搜「TypeError」) |
| 官方文档链接 | SECTION IV: Data Collections | text/25-fm-section-iv-data-collections.txt:735(搜「EXPLORE MORE」) |
| 章节引言(接在 defaultdict 之后) | SECTION IV: Data Collections | text/25-fm-section-iv-data-collections.txt:514(搜「key-value pairs efficiently」) |
Footnotes
-
出处:「SECTION IV: Data Collections」第 512–523 段(
text/25-fm-section-iv-data-collections.txt:516,搜「Taking this further」;:517,搜「designed specifically for counting」)。原文章节引言:「the Counter is a specialized type of dictionary designed specifically for counting tasks」。 ↩ -
出处:「SECTION IV: Data Collections」第 525–567 段(
text/25-fm-section-iv-data-collections.txt:527,搜「fruit count exercise」;:540,搜「'banana': 3」;:542,搜「save some effort」;:563,搜「Counter(basket)」;:567,搜「'banana': 3」)。同一篮水果(basket,第 530 段)的三级写法与运行结果并排,结果一致:banana 3、apple 2、orange 1。 ↩ ↩2 ↩3 ↩4 -
出处:「SECTION IV: Data Collections」第 607–617 段(
text/25-fm-section-iv-data-collections.txt:610,搜「powerful programming language」;:613,搜「replace」;:617,搜「Top 5」)。数字母前的清洗(去空格、转小写)与most_common(5)的运行结果。 ↩ ↩2 -
出处:「SECTION IV: Data Collections」第 697–707 段(
text/25-fm-section-iv-data-collections.txt:697,搜「ROMEO AND JULIET」;:706,搜「shakespeare.mit.edu」;:707,搜「not provided」)。案例要求找全文、喂给 Counter 统计词频;原文明说「To save space, solution for this case study is not provided」。 ↩ ↩2 -
出处:「SECTION IV: Data Collections」第 619–695 段(
text/25-fm-section-iv-data-collections.txt:664,搜「paragraph.lower().split()」;:669,搜「python: 9」;:681,搜「1, 3, 2, 2」;:695,搜「(2, 4)」)。词频榜前 10 与数字榜前 3 的运行结果。 ↩ ↩2 -
出处:「SECTION IV: Data Collections」第 569–576 段(
text/25-fm-section-iv-data-collections.txt:572,搜「subclass of dict」;:575,搜「most_common」)。「counts the occurrences of elements without needing a(数元素的次数,不需要循环)」见第 557 段。 ↩ -
出处:「SECTION IV: Data Collections」第 726–733 段(
text/25-fm-section-iv-data-collections.txt:730,搜「TypeError」;:731,搜「non-integer」;:732,搜「negative counts」)。可哈希要求、非整数增量、负计数都只在「INTERACT WITH GENAI」的提问清单里出现,正文未展开;官方文档链接见第 735–739 段。 ↩ ↩2 ↩3