一行dict.get()轻松搞定Python计数统计
如果你写代码时遇到“统计一个列表里每个元素出现了几次”“数一数一段文本里每个字符的频率”“盘点日志里各种级别出现了多少条”这类需求第一反应多半是循环加判断再手动维护一个字典。做得多了你会发现Python 里最顺手的计数统计写法其实是一行dict.get()。这个技巧并不高深但能让代码从四五行缩到一行而且逻辑清楚、不容易出错我自己在项目里用它替换了大量手工计数代码。这篇文章会从最普通的实现方式讲起拆解dict.get()一行计数的原理对比get、setdefault、defaultdict、Counter这几个容易混淆的方案再给出一批可以直接用的实战例子和踩坑记录。无论你是刚接触 Python 的初学者还是写了好几年代码的老手应该都能从中拿到一点能直接用上的东西。1. 我为什么盯上dict.get()这个写法1.1 先看看大多数人是怎么写计数统计的假设有个需求给出一份城市名单统计每个城市出现了多少次。最直白的实现大概是这样的cities [北京, 上海, 广州, 北京, 深圳, 上海, 北京] counter {} for city in cities: if city in counter: counter[city] 1 else: counter[city] 1 print(counter) # {北京: 3, 上海: 2, 广州: 1, 深圳: 1}逻辑没有任何问题先看键在不在字典里在就加一不在就初始化成 1。当年我也是这么写的写多了就覺得这段代码又长又重复每一处计数都要写一遍if...else...。后来有一阵我改用try...except...counter {} for city in cities: try: counter[city] 1 except KeyError: counter[city] 1这是用异常处理来代替判断。对单个键来说抛异常的成本比较高如果键大量不存在性能反而不理想而且代码读起来绕新人看到KeyError还要想一下这是哪来的。这种写法适合某些特殊场景但作为通用计数方案并不算好。1.2 这些老写法到底有什么问题第一种if...else...写法最大的问题是重复。每一次“计数”这个动作都要准备两份分支逻辑。一个脚本里如果有五个地方需要计数那就要写五段几乎一样的代码后面维护的人看着就想重构。第二个问题也是最容易被忽略的当初始值不是 1而是其他值的时候if...else...会变得更啰嗦。比如统计总分scores {} for name, score in data: if name in scores: scores[name] score else: scores[name] score这就不是“加一”了而是“存在就累加不存在就赋值”。分支结构会更明显。我真正开始留心dict.get()是因为有一次需要统计一份十几万行日志里的错误级别。当时想都没想就写了if...else...回头去看那段代码的时候突然意识到这一段逻辑明明只是一个“往字典里累加一个值”的动作为什么要写这么多行于是我把那段代码改成了dict.get()的版本这下整个人舒服了。2. 一行计数统计的正确打开方式2.1 核心代码拆解counter[city] counter.get(city, 0) 1直接看代码cities [北京, 上海, 广州, 北京, 深圳, 上海, 北京] counter {} for city in cities: counter[city] counter.get(city, 0) 1 print(counter) # {北京: 3, 上海: 2, 广州: 1, 深圳: 1}循环体里只有一行counter[city] counter.get(city, 0) 1。这一行做了三件事counter.get(city, 0)从字典里取当前城市的计数值。如果这个城市还是第一次出现字典里没有这个键就返回默认值 0。 1在取出来的值基础上加一得到新的计数值。counter[city] ...把新值写回字典覆盖原来的计数值。注意get和直接用中括号取值的区别在于counter[city]在键不存在时会抛出KeyError而counter.get(city, 0)不会抛异常只会返回一个默认值。这就是能用“一行”完成计数而不需要任何分支的根本原因。这一行的等价写法其实就是最早的if...else...版本只是把“判断键是否存在”这件工作交给了get内部的默认值机制。代码短了但逻辑没有打折。2.2 三个最常见的实际场景这个模式可以套进很多需求里。比如统计字符串中每个字符的出现次数text hello world char_count {} for ch in text: char_count[ch] char_count.get(ch, 0) 1 print(char_count) # {h: 1, e: 1, l: 3, o: 2, : 1, w: 1, r: 1, d: 1}又比如统计一个列表里每个单词的词频words [apple, banana, apple, orange, banana, apple] word_count {} for word in words: word_count[word] word_count.get(word, 0) 1 print(word_count) # {apple: 3, banana: 2, orange: 1}再比如统计日志中不同错误级别的条数log_levels [ERROR, INFO, DEBUG, ERROR, INFO, ERROR] level_count {} for level in log_levels: level_count[level] level_count.get(level, 0) 1 print(level_count) # {ERROR: 3, INFO: 2, DEBUG: 1}同样的套路换一个数据结构需求就完成了。不需要if不需要except不需要每次循环前先初始化。2.3 默认值这个参数是整个逻辑的灵魂dict.get(key, default)的第二个参数很关键。很多人知道get可以传默认值但用到计数上时容易犯一个错误忘了传默认值直接写counter.get(city) 1。这种写法会报错。因为当city不存在时get返回的是NoneNone 1直接抛出TypeError: unsupported operand type(s) for : NoneType and int。默认值填 0 的语义是这个键如果不存在就把它当成“已经出现过 0 次”来处理然后加上本次的 1 次。这一步想清楚了整个一行写法就很好理解。还有一个小细节如果你要累加的不是 1而是某个权重也可以直接改。比如统计一个店铺各商品的销售额transactions [(sku123, 199), (sku456, 99), (sku123, 50)] sales {} for sku, amount in transactions: sales[sku] sales.get(sku, 0) amount print(sales) # {sku123: 249, sku456: 99}这个例子能说明get方案真正的价值它不是只能“加一”而是“取旧值加上新值”所以计数、求和、累加都能用它来写。只要把 1换成 amount就行。3. 它为什么能这样写原理与兄弟方法对比3.1 一次调用背后到底发生了什么dict.get()是字典对象的内置方法它的行为可以简单理解为第一个参数是键名。如果键存在于字典中返回键对应的值。如果键不存在且传了第二个参数返回第二个参数没传则返回None。关键在于get只负责“读”它不会修改字典本身。不管键存不存在调用get都不会往字典里写入任何东西。真正产生写入动作的是那条赋值语句counter[city] ...。这个特性让它和setdefault有着本质区别。setdefault是“读不到就写入一个默认值再返回”它是有副作用的get是无副作用的。在计数场景里如果每一轮循环都用setdefault即使键已经存在它也会反复构建默认值对象白白浪费性能。get就不会有这个问题。还有一个相关方法是pop(key, default)它也是带默认值机制的方法但它的行为是“取走并删除”。计数的时候千万别用它否则统计到一半键被删掉了数据全丢。3.2get、setdefault、defaultdict、Counter到底怎么选写计数的方法不止get一种。我整理了一下这几个方案的对比方便以后选型方案典型写法特点适用场景dict.getcounter[k] counter.get(k, 0) 1无副作用不额外导入写法直观通用计数、累加、求和最推荐setdefaultcounter.setdefault(k, 0); counter[k] 1两步操作且每次都构造默认值需要默认容器时如列表会用到defaultdictcounter defaultdict(int); counter[k] 1导入collections键不存在时自动创建字典里的值就是可变容器时很好用CounterCounter(iterable)专门为计数设计自带most_common等方法纯频次统计不需要自定义逻辑时首选defaultdict(int)写起来也很短但它多了一个导入而且如果后续有人不小心访问了一个不存在的键字典会默默把这个键创建出来有时候反而掩盖了代码里的问题。Counter是做纯计数的最专业方案一行Counter(words)就能完成统计但当你需要在循环里逐条累加、需要同时维护多个维度计数器时灵活度不如dict.get()。我个人的习惯是如果只是给一个现成的列表做频次统计直接Counter如果是循环里逐步更新计数、累加金额、在多个字段上做统计用dict.get()最顺手。3.3 性能与可读性的平衡有人会问dict.get()这个写法在性能上是不是比if...else...慢说实话影响微乎其微。在 Python 里get本身是 C 语言实现的哈希查找和in判断加下标取值相比核心操作都是哈希查找。硬要比较的话get只做一次哈希查找if...else...在某些路径下要做两次先in判断再counter[city] 1取值所以get在“键已存在”的高频场景下反而可能更快一点。当然这种差异在你统计一万个元素的时候根本感觉不出来。真正值得注意的是另一个极端千万不要为了“一行”而写这种代码[word_count.update({w: word_count.get(w, 0) 1}) for w in words]这种用列表推导式的副作用来实现计数的写法虽然真的只有一行但它是错的。它生成了一个全是None的临时列表纯属浪费内存而且可读性极差。记住“一行代码”正确的追求是“一个动作只写一行”不是“整段逻辑挤在一行”。4. 一行计数还能延伸出哪些变体4.1 一行完成词频统计并按频次排序计数只是第一步统计完往往还要排序。有了get的基础整条链路也可以写得很紧凑words [apple, banana, apple, orange, banana, apple] counter {} for word in words: counter[word] counter.get(word, 0) 1 # 按频次从高到低排序 sorted_counter sorted(counter.items(), keylambda x: x[1], reverseTrue) print(sorted_counter) # [(apple, 3), (banana, 2), (orange, 1)]取出现次数最多的前两个元素top2 sorted(counter.items(), keylambda x: x[1], reverseTrue)[:2] print(top2) # [(apple, 3), (banana, 2)]这里的sorted(counter.items(), keylambda x: x[1])是按字典的值排序reverseTrue表示降序。这类写法在分析日志、统计热词、排行榜场景里非常常用。除了sorted还可以用max(counter, keycounter.get)直接拿到出现次数最多的键most_common_word max(counter, keycounter.get) print(most_common_word) # apple这里max的key参数接收一个函数counter.get会取出每个键对应的频次max再根据这个频次找出最大的键。这个用法是dict.get()的另一个经典应用也值得记下来。4.2 合并两个字典的计数结果有时候数据被拆成了两份比如两天各自的访问统计你想把它们合并成一份总数。用get写起来也很自然day1 {u001: 5, u002: 3} day2 {u002: 2, u003: 4} merged {k: day1.get(k, 0) day2.get(k, 0) for k in set(day1) | set(day2)} print(merged) # {u001: 5, u002: 5, u003: 4}set(day1) | set(day2)是取两个字典所有键的并集然后对每个键分别从两张表里取值并用默认值 0 兜底。这里的核心技巧依然是get不管这个键在不在原来的字典里都能得到一个合理的数字。如果不用get写day1[k] day2[k]会频繁触发KeyError要么就得写一堆判断。用get直接把合并逻辑压成一行字典推导式结果清晰又不会报错。4.3 嵌套字典的分组计数回到文章开头提到的“统计每个用户的各种行为次数”这种需求会涉及嵌套字典。先看一个很容易想到的写法events [ (u001, login), (u002, page_view), (u001, logout), (u001, login), ] action_count {} for user, action in events: if user not in action_count: action_count[user] {} action_count[user][action] action_count[user].get(action, 0) 1 print(action_count) # {u001: {login: 2, logout: 1}, u002: {page_view: 1}}这个写法里外层还保留了一个if因为我们要用一个空字典作为内层默认值。如果想把这段也简化可以使用setdefaultfor user, action in events: action_count.setdefault(user, {}) action_count[user][action] action_count[user].get(action, 0) 1或者用defaultdict(dict)from collections import defaultdict action_count defaultdict(dict) for user, action in events: action_count[user][action] action_count[user].get(action, 0) 1这三种写法都可以。内层的action_count[user].get(action, 0) 1依然是我们那套一行计数外层只是在解决“内层字典还不存在”的问题。真实项目中像“用户行为统计”“按省份统计各品类销量”这类二维数据用这个嵌套模式非常顺手。4.4 列表扁平化与其他场景里的变通用法dict.get()除了计数在配置读取、缓存取值等场景也经常出现。比如从配置字典里读取某个参数找不到就返回默认值config {host: localhost, port: 8080} timeout config.get(timeout, 30)这几乎是最常见的一种用法想要一个“存在就用配置值不存在就用默认值”的读取逻辑写的正是config.get(key, default)。这和计数时的思路完全一致只是返回值不参与加一操作。再比如把两个列表按元素分组也可以用get和列表拼接的方式data [(group_a, 1), (group_b, 2), (group_a, 3)] grouped {} for group, value in data: grouped[group] grouped.get(group, []) [value] print(grouped) # {group_a: [1, 3], group_b: [2]}注意这种写法会反复创建新列表数据量大的时候不如setdefault或defaultdict(list)高效但胜在代码短且无额外导入。在数据量可控的脚本里用一用完全没问题。5. 实战中容易踩的坑与排查思路5.1 默认值不能随便填可变对象陷阱get的默认值虽然方便但第二个参数是一个普通值。假设你写了这么一段代码想把用户分到不同分组里groups {} for name in user_list: groups[name] groups.get(name, []) [member]功能上没问题但请注意这里的默认值[]每次调用get时都会被重新创建且 [member]也是新创建一个列表整体效率不算高。如果列表很大会明显浪费内存。相比之下setdefault和defaultdict(list)是在原列表上做append效率更高更适合这种“默认值是可变容器”的场景。一句话总结计数加数字用get默认容器要原地修改时用setdefault或defaultdict。5.2 忘写第二个参数直接 TypeError这是我看到新手甚至老手最容易踩的坑。当键不存在时get(key)返回None如果接着做 1就会抛出TypeError。排查的时候看到报错信息第一反应往往以为是数据类型不匹配实际上是因为默认值没写。调试这种问题有一个经验在计数代码里看到get方法先检查第二个参数是否写了再检查返回值参与运算的类型是否一致。把这一条养成习惯这类报错基本一眼就能定位。5.3 字符串统计时的隐藏字符统计一段文本里字符频率的时候for ch in text会把空格、换行符、制表符也当作普通字符统计进去。你要是对比两份统计结果明明只改了排版频率却对不上十有八九是换行和空格的问题。如果需要忽略空白字符可以先用过滤器处理一下text hello world \n this is a test char_count {} for ch in text: if ch.strip(): char_count[ch] char_count.get(ch, 0) 1ch.strip()可以判断当前字符是否为空字符。这个细节在实际处理爬虫抓回来的网页文本时特别重要因为 HTML 里有大量空白字符会干扰词频分析。5.4 别在列表推导式里硬塞计数前面提到过用列表推导式的副作用做统计是不可取的。除此之外还有一种看起来“聪明”的写法# 反例不推荐 {words.count(w): w for w in words}如果只统计不排序collections.Counter显然更合适。如果你在代码审查里看到某段计数逻辑写得很绕先帮它简化成counter[k] counter.get(k, 0) 1再看看绝大多数时候这个简单版本已经够用了。我后来在项目里养成一个习惯只要看到“循环里维护字典并做累加”的需求先写一行dict.get()版本然后再判断是否需要升级成Counter或defaultdict。用最简单的方式开始实在不够用了再升级基本不会有大问题。平时做日志统计、爬虫去重、用户行为分析这类事情counter[key] counter.get(key, 0) 1已经足够顺手。偶尔需要统计完立刻取最高频项再用max(counter, keycounter.get)补一行。这两个写法配合起来足以应付绝大多数计数需求而且每一行都看得懂不会给自己留坑。

相关新闻

SpringBoot+Vue医院预约系统高并发架构实践

SpringBoot+Vue医院预约系统高并发架构实践

1. 项目概述:医疗预约系统的技术架构与价值这个基于SpringBootVue的医院预约系统,是我去年为本地一家三甲医院交付的线上挂号平台。系统上线后,门诊预约率提升了47%,护士站电话咨询量减少了60%。这种前后端分离的架构设计&#xf…

2026/10/14 8:07:49 阅读更多 →
普通鼠标告别生硬滚动——Mac Mouse Fix 让百元鼠标拥有触控板手感

普通鼠标告别生硬滚动——Mac Mouse Fix 让百元鼠标拥有触控板手感

普通鼠标告别生硬滚动——Mac Mouse Fix 让百元鼠标拥有触控板手感 【免费下载链接】mac-mouse-fix Mac Mouse Fix - Make Your $10 Mouse Better Than an Apple Trackpad! 项目地址: https://gitcode.com/GitHub_Trending/ma/mac-mouse-fix 你盯着滚轮一下下"咔哒…

2026/10/14 8:07:49 阅读更多 →
用消息队列跑批量 LLM 任务:异步、进度、重试、限流实战

用消息队列跑批量 LLM 任务:异步、进度、重试、限流实战

授权与合规声明 本文为技术实操分享,所有示例均在自建隔离环境中验证或为可复现的演示代码;文中版本、命令与数据均标注来源,无法核验的已标「待验证」,不含任何攻击步骤、利用载荷与绕过手法。转载请注明出处。一、批量调大模型为…

2026/10/14 8:07:49 阅读更多 →

最新新闻

Three.js加载Gltf模型并播放动画

Three.js加载Gltf模型并播放动画

老规矩:初始化相机,场景,渲染器等,这里不过多赘述,详情看上一篇文章。 从0搭建一个3D立体场景:光影与交互全解析-CSDN博客 基本使用 只要在文件中引入这个即可: const loader new GLTFLoad…

2026/10/14 9:05:48 阅读更多 →
GESP三级考试数字图案打印题解析与实现

GESP三级考试数字图案打印题解析与实现

1. 题目背景与需求分析 这道来自GESP202412三级考试的编程题目,考察的是考生对循环结构和格式化输出的掌握程度。题目要求我们根据输入的数字n,打印出特定的数字图案。这类题目在编程初学者中非常常见,它不仅能检验基础语法掌握情况&#xff…

2026/10/14 9:05:48 阅读更多 →
STM32 HAL库红外遥控解码:定时器捕获与状态机实战

STM32 HAL库红外遥控解码:定时器捕获与状态机实战

简介:这是一份面向嵌入式初学者与STM32开发者的红外遥控解码实践资源,基于HAL库实现红外信号的接收、定时器捕获与串口输出,帮助读者理解NEC等红外协议的脉冲解码流程,适合作为外设驱动入门与课程设计的参考工程。压缩包共79个文件…

2026/10/14 9:05:48 阅读更多 →
基于Web的PS5辅助管理工具:架构设计与多主机数据聚合实践

基于Web的PS5辅助管理工具:架构设计与多主机数据聚合实践

不知道从什么时候开始,主机玩家的"资产"越来越数字化了。过去是货架上一排排光碟,现在是游戏库里的几百个图标、动不动就爆的固态硬盘、以及每次开机都要等半天的下载队列。我自己就是典型的重度PS5玩家,游戏买了不少,真…

2026/10/14 9:05:48 阅读更多 →
Python数字图案打印:循环结构与格式化输出实践

Python数字图案打印:循环结构与格式化输出实践

1. 项目背景与需求解析 数字图案打印是编程初学者常见的练习题型,也是培养逻辑思维能力的绝佳途径。这道B4067题目要求我们根据输入的数字n,打印出特定的数字图案。这类题目看似简单,实则蕴含了多重编程思维训练价值。 从教学角度来看&#…

2026/10/14 9:05:48 阅读更多 →
微信小程序点餐系统开发与优化实践

微信小程序点餐系统开发与优化实践

1. 项目背景与核心价值在餐饮行业数字化转型的大趋势下,咖啡店这类高频消费场景对点餐效率的要求越来越高。传统纸质菜单人工点单的模式存在三个痛点:高峰期排队时间长、人工记录易出错、会员营销难触达。我们团队开发的这套微信小程序点餐系统&#xff…

2026/10/14 9:04:46 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →