直接用 dict 手动计数易出错,因需手动处理键存在性、拼写错误及可变默认参数问题,而 Counter 重写 missing 默认返回 0,支持安全累加、高效 O(n) 统计、内置运算与高频查询。

为什么直接用 dict 手动计数容易出错
很多人第一反应是遍历列表,对每个元素手动在 dict 里做 if key in d: d[key] += 1 else: d[key] = 1。这逻辑没错,但容易漏掉初始化、拼错键名,或在嵌套结构里误用可变默认参数。更关键的是——它不处理缺失键的异常场景,比如用 d[key] += 1 前没检查是否存在,会直接抛 KeyError。
而 Counter 本质是 dict 的子类,但重写了 __missing__ 方法,默认返回 0,所有累加操作天然安全。
-
Counter()构造时接受任意可迭代对象,包括字符串(按字符计)、元组、甚至生成器 - 支持直接相加:
c1 + c2,也支持减法和取交集/并集(&/|) - 调用
most_common(n)可快速拿到前n高频项,返回list[tuple],不是字典,注意类型
Counter 和 dict.fromkeys + list.count 的性能差距在哪
list.count(x) 对每个唯一元素都扫一遍原列表,时间复杂度是 O(n × k),k 是去重后元素个数;而 Counter 只遍历一次,O(n)。当列表有 10 万个元素、重复率高时,后者快 5–10 倍是常态。
另外,dict.fromkeys(items, 0) 只能设初值,不能自动累加,仍需二次遍历更新,没省事。
立即学习“Python免费学习笔记(深入)”;
- 别用
[x for x in set(lst) if lst.count(x) > 1]查重复项——这是反模式,既慢又无法统计频次 - 如果只需要判断某个元素是否出现 ≥2 次,用
Counter(lst)[target] >= 2比反复count更稳 -
Counter内部用 C 实现了大部分逻辑,比纯 Python 循环快
遇到不可哈希元素(如字典、列表)怎么办
Counter 要求元素可哈希,否则构造时直接报 TypeError: unhashable type。比如 Counter([{'a': 1}, {'a': 1}]) 就不行。
解决思路不是硬转字符串(会丢失语义、且 {'a':1, 'b':2} 和 {'b':2, 'a':1} 字符串不同但逻辑等价),而是先标准化再计数:
- 对字典:用
json.dumps(d, sort_keys=True)转成确定性字符串 - 对列表:若元素顺序无关,先
tuple(sorted(lst));若有关,直接tuple(lst) - 更健壮的做法是封装一个函数,统一处理嵌套结构,避免全局替换
list或dict
注意:这种转换本身有开销,仅在必须统计时才做,别当成默认流程。
从 Counter 输出结果到实际业务的常见断点
很多人卡在“得到了 Counter 对象,然后呢?”。它不像 dict 那样直接 JSON 序列化(因为是子类,某些旧版 json.dumps 会报错),也不直接支持 pandas Series 构造(虽然新版 pandas 支持,但老版本需转 dict)。
- 转标准字典:显式调用
dict(counter_obj),别依赖隐式转换 - 写入 JSON:先
dict(counter_obj),或自定义json.JSONEncoder子类 - 给 pandas:
pd.Series(counter_obj)可行,但索引是元素值,值是频次;若要 DataFrame,用pd.DataFrame(list(counter_obj.items()), columns=['element', 'count']) -
Counter的.elements()方法返回迭代器,不是列表,需要list(c.elements())才能展开,别直接 print
最常被忽略的是:Counter 对空输入(如空列表)返回空实例,但 most_common(1) 会返回空列表,不是 None,业务代码里别用 if counter.most_common(1): 判非空——应该用 if counter:。


















