别再手写循环了:Python 标准库里藏着的 8 个神器
· 阅读需 4 分钟

很多人学 Python 学到能写循环,就开始「自己造轮子」:统计词频写个字典加 if、递归算斐波那契慢到怀疑人生、拼路径用字符串加斜杠……
其实这些活儿标准库早就替你干好了。今天村长挑 8 个我真正在项目里反复用的,都是开箱即用、不用装第三方包的。
1. Counter:数数这件事别自己写
from collections import Counter
words = ["python", "code", "python", "tips", "code", "python"]
c = Counter(words)
print(c) # -> Counter({'python': 3, 'code': 2, 'tips': 1})
print(c.most_common(2)) # -> [('python', 3), ('code', 2)]
print(c["java"]) # -> 0,不存在的键不会报 KeyError
most_common()、+、-、集合运算全都支持,统计词频一行搞定。
2. defaultdict:给字典一个「默认值工厂」
from collections import defaultdict
# 按首字母分组
groups = defaultdict(list)
for w in ["apple", "banana", "avocado", "blueberry"]:
groups[w[0]].append(w)
print(dict(groups))
# -> {'a': ['apple', 'avocado'], 'b': ['banana', 'blueberry']}
不用再写「先判断键在不在,再决定要不要建空列表」那三行了。
村长划重点:
defaultdict只在访问不存在的键时补默认值,已经存在的键不受影响。它和dict.get()不是一回事:前者会真的把键写进字典。
3. deque:队列要专业一点的
from collections import deque
q = deque(["任务1", "任务2"])
q.append("任务3")
q.popleft() # -> '任务1',O(1)
print(q) # -> deque(['任务2', '任务3'])
q.appendleft("插队")
q.rotate(1) # 循环右移一位
用 list.pop(0) 做队列,元素越多越慢(要整体搬移);deque 两端操作都是常数时间。
4. functools.lru_cache:一行提速递归
from functools import lru_cache
@lru_cache(maxsize=None)
def fib(n):
return n if n < 2 else fib(n - 1) + fib(n - 2)
print(fib(100)) # 瞬间出结果,不加缓存会算到天荒地老
print(fib.cache_info())
适合「同样的输入会得到同样输出」的函数:递归、配置读取、接口结果缓存。
5. itertools:循环的组合技
import itertools
# 拍平嵌套列表
nested = [[1, 2], [3, 4], [5]]
print(list(itertools.chain.from_iterable(nested)))
# -> [1, 2, 3, 4, 5]
# 笛卡尔积,代替两层 for
print(list(itertools.product(["S", "M"], ["红", "蓝"])))
# -> [('S', '红'), ('S', '蓝'), ('M', '红'), ('M', '蓝')]
# 按奇偶分组(记得先排序)
data = [1, 1, 2, 3, 4, 4]
for k, g in itertools.groupby(data, key=lambda x: x % 2):
print(k, list(g))
还有 islice、cycle、combinations、permutations,基本能覆盖你手写循环的大部分理由。
6. pathlib:路径别再用字符串拼了
from pathlib import Path
p = Path("项目") / "data" / "users.json"
print(p) # -> 项目/data/users.json
p.parent.mkdir(parents=True, exist_ok=True)
if p.exists():
text = p.read_text(encoding="utf-8")
for f in Path(".").glob("*.md"):
print(f.name)
/ 运算符会自动处理不同系统的分隔符,Windows 上也不会翻车。
7. statistics:算个平均值不必引 numpy
from statistics import mean, median, stdev, mode
scores = [88, 92, 75, 92, 100]
print(mean(scores)) # -> 89.4
print(median(scores)) # -> 92
print(mode(scores)) # -> 92
print(stdev(scores)) # -> 9.15 左右
小脚本里为了一个平均值装 numpy,属实没必要。
8. contextlib.suppress:优雅地忽略特定异常
import contextlib
import os
# 绕的写法
try:
os.remove("临时文件.tmp")
except FileNotFoundError:
pass
# 村长写法
with contextlib.suppress(FileNotFoundError):
os.remove("临时文件.tmp")
只吞掉你明确列出的异常,其他错误照常抛——比裸 except: pass 安全得多。
小结
| 你的需求 | 别再手写 | 直接用 |
|---|---|---|
| 统计频次 | 字典加 if | Counter |
| 按键分组 | 判断键是否存在 | defaultdict |
| 先进先出队列 | list.pop(0) | deque |
| 重复计算 | 自己存缓存字典 | lru_cache |
| 嵌套循环拍平 | 双层 for | itertools |
| 拼文件路径 | 字符串加斜杠 | pathlib |
| 基础统计 | 手写求和 | statistics |
| 忽略某个异常 | try/except/pass | contextlib.suppress |
村长划重点:标准库的判断标准很简单——你正准备写的那段「通用工具代码」,十有八九已经有人写好了。动手前先搜一下官方文档,比写完再重构省时间。
这 8 个只是冰山一角。想系统过一遍标准库,可以接着看我们教程里的「标准库速览(一)(二)」两章。
我是村长,下篇聊聊怎么少写 print、早点下班。
