编程学习网 > 编程语言 > Python > Python 3.15 Counter 这个新运算符救了多少新手?再也不用为对比数据发愁了
2026
07-03

Python 3.15 Counter 这个新运算符救了多少新手?再也不用为对比数据发愁了


你有没有遇到过这种情况:写了个爬虫抓了两天的数据,想看看两天的热门词汇有什么变化?或者做了A/B测试后,想一眼看出两个实验组的行为分布差在哪?

这时候你八成会用到 Python collections.Counter——但问题来了:Counter 有加法(合并)、减法(求差)、交集(取共同部分)、并集(取覆盖范围),偏偏没有一个直接"对比差异"的运算符。你只能自己写循环一个一个比。

Python 3.15 终于把这个缺口补上了——Counter 新增了 ^(异或)运算符,专门做"对称差集"。一行代码,两份数据差异全出来。

Counter 到底是什么?

先花一分钟回忆一下。Counter Python 标准库中最被低估的工具之一,它本质上就是一个"计数器字典"——帮你统计每个元素出现了多少次。

from collections import Counter

words = ['python', 'java', 'python', 'cpp', 'java', 'python']

word_count = Counter(words)

print(word_count)

# Counter({'python': 3, 'java': 2, 'cpp': 1})

几行代码就能完成词频统计,比你自己写 for 循环加字典累加舒服多了。而且 Counter 不止能做统计,它还支持集合运算——这是它最强大的地方。

Counter 已有的四大运算符

3.15 之前,Counter 已经支持四种集合运算,每种对应一个数学运算符:

from collections import Counter

 

c = Counter(a=3, b=1)

d = Counter(a=1, b=2)

 

c + d   # Counter({'a': 4, 'b': 3})  加法:计数直接相加

c - d   # Counter({'a': 2})         减法:只保留正结果

c & d   # Counter({'a': 1, 'b': 1})  交集:取最小计数

c | d   # Counter({'a': 3, 'b': 2})  并集:取最大计数

这四个运算符让 Counter 像数学集合一样优雅,日常的数据统计工作基本够用。

但是,有一个非常常见的需求它一直没解决:两份数据"差在哪"

痛点:找差异只能手写循环

举个例子:你统计了昨天和今天的热搜词频——

yesterday = Counter({'AI': 5, 'Python': 3, 'Java': 2})

today = Counter({'AI': 4, 'Python': 5, 'Go': 3})

你想知道哪些词在两天的热度不一样?AI 昨天 5 次今天 4 次,差了 1Python 昨天 3 次今天 5 次,差了 2Go 新出现在今天,差了 3...

3.15 之前,你只有两种办法:

办法一:老老实实手写循环——

diff = Counter()

for key in set(yesterday) | set(today):

    diff[key] = abs(yesterday[key] - today[key])

办法二:用并集减去交集来间接达成——

diff = (yesterday | today) - (yesterday & today)

两种办法都能用,但都不够直接。办法一啰嗦,办法二虽然一行但语义不直观——你明明是在"找差异",却用了并集和交集来间接实现。每次写到这都要顿一下想想逻辑对不对。

Python 3.15 的答案:^ 运算符

3.15 Counter 加上了 ^(异或,也叫对称差集)运算符。一句话解释它的语义:

^ = 两个 Counter "不一样的部分" = 并集 - 交集

用代码表示就是:c ^ d = (c | d) - (c & d)

拿刚才的热搜例子试一下:

yesterday = Counter({'AI': 5, 'Python': 3, 'Java': 2})

today = Counter({'AI': 4, 'Python': 5, 'Go': 3})

 

diff = yesterday ^ today

print(diff)

# Counter({'Go': 3, 'Python': 2, 'AI': 1, 'Java': 2})

来逐项解读:

Go:昨天没有,今天 3 次,差异 = 3(新出现的词)

Python:昨天 3 次,今天 5 次,差异 = 2(热度上升)

AI:昨天 5 次,今天 4 次,差异 = 1(热度略降)

Java:昨天 2 次,今天没有,差异 = 2(消失的词)

一行代码,所有差异一目了然。而且 ^ 对称的yesterday ^ today == today ^ yesterday),不像减法是单向的。你不用关心谁在左边谁在右边,结果一样。

三个实战场景

场景一:A/B 测试数据对比

你做了一周的 A/B 测试,每天记录用户点击的按钮。想一眼看出两个版本的用户行为差在哪:

ver_a = Counter({'首页': 120, '搜索': 80, '设置': 30})

ver_b = Counter({'首页': 100, '搜索': 95, '关于': 25})

 

ver_a ^ ver_b

# Counter({'设置': 30, '关于': 25, '首页': 20, '搜索': 15})

设置页面在 A 版多了 30 次点击,而 B 版多了"关于"页面——两种设计引导了不同的用户行为,数据清清楚楚。

场景二:代码仓库变更统计

你从 GitHub 拉了两个版本的代码,想快速看看哪些文件有增删改:

v1_files = Counter({'main.py': 3, 'utils.py': 2, 'test.py': 1})

v2_files = Counter({'main.py': 5, 'utils.py': 1, 'new.py': 4})

 

v1_files ^ v2_files

# Counter({'new.py': 4, 'main.py': 2, 'utils.py': 1, 'test.py': 1})

new.py 是新增文件,main.py 改动最大,utils.py 有些微调,test.py 被删掉了——一次代码 review 的准备工作,一行搞定。

场景三:日志关键词趋势监控

你每天跑的日志分析脚本,想找出"今天和昨天比,哪些错误类型变化最大"

y_errors = Counter({'Timeout': 15, 'DBConn': 8, 'NullPtr': 3})

t_errors = Counter({'Timeout': 20, 'DBConn': 5, 'OOM': 6})

 

y_errors ^ t_errors

# Counter({'OOM': 6, 'Timeout': 5, 'DBConn': 3, 'NullPtr': 3})

OOM 是今天新出现的错误类型,出现了 6 ——必须立刻排查;Timeout 增加了 5 ——也需要关注;NullPtr 消失了——说明昨天的修复生效了。

这三个场景有一个共同点:Counter ^ 运算符都不是"必须的",你完全可以用手写循环或 | - & 组合来完成。但一旦你习惯了用 ^ 一行搞定,回头看那些三行五行的手动方案,就会觉得——这功能早该有了。

新手容易踩的 3 个坑

坑一:^ - 不一样,别搞混

- 是单向的:只保留"左边比右边多"的部分。^ 是双向的:两边所有的差异都保留。

c = Counter(a=5, b=1)

d = Counter(a=2, b=4)

 

c - d  # Counter({'a': 3})         只保留 c d 多的

c ^ d  # Counter({'a': 3, 'b': 3})  两边的差异都保留

简单判断:如果你只关心"左边多出来的那部分",用 -;如果你关心"两边不一样的全部",用 ^

坑二:两个 Counter 完全相同时,^ 结果是空的

这个不是 bug,但第一次遇到可能会愣一下:

a = Counter(x=1, y=2)

b = Counter(x=1, y=2)

a ^ b  # Counter()  完全一样,没有差异,结果是空的

这完全合理——你问"两份数据有什么不同",答案是"没有不同",返回空 Counter 再自然不过。

坑三:只处理正整数,0 和负数被忽略

Counter 的所有运算符都只处理正整数值。如果元素的值是 0 或负数,这些元素会被当作"不存在"来处理:

c = Counter(a=0, b=3)   # a 的值是 0,会被忽略

d = Counter(a=5, b=3)

c ^ d  # Counter({'a': 5})  相当于 Counter(b=3) ^ Counter(a=5, b=3)

Counter 设计上就只关心"出现了几次"这种场景。如果需要处理负数或零值,换个数据结构。

小结:五个运算符,Counter 毕业了

Python 3.15 Counter 新增的 ^ 运算符,虽然不是什么惊天动地的大功能,但它补齐了"找差异"这个缺口,让 Counter 的集合运算终于完整了。

来一张速查表,五个运算符一网打尽:

加法 —— 合并两个计数,各元素直接相加

减法 —— 左减右,只保留正结果(有方向)

交集 —— 取每个元素的最小计数,"共同拥有多少"

并集 —— 取每个元素的最大计数,"总共涉及多少"

对称差 —— 并集减交集,"不一样的部分"(无方向)

下次需要对两份数据进行差异对比的时候,别写循环了,试试 Counter ^,一行搞定。

你有没有遇到过需要对比两份数据差异的场景?之前是怎么解决的?评论区聊聊你的方法。

以上就是“Python 3.15 Counter 这个新运算符救了多少新手?再也不用为对比数据发愁了的详细内容,想要了解更多Python教程欢迎持续关注编程学习网。   

扫码二维码 获取免费视频学习资料

Python编程学习

查 看2022高级编程视频教程免费获取