
你有没有遇到过这种情况:写了个爬虫抓了两天的数据,想看看两天的热门词汇有什么变化?或者做了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 次,差了 1;Python 昨天 3 次今天 5 次,差了 2;Go 新出现在今天,差了 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教程欢迎持续关注编程学习网。
扫码二维码 获取免费视频学习资料

- 本文固定链接: http://www.phpxs.com/post/14283/
- 转载请注明:转载必须在正文中标注并保留原文链接
- 扫码: 扫上方二维码获取免费视频资料