跳到主要内容

别再手写循环了:Python 标准库里藏着的 8 个神器

· 阅读需 4 分钟
村长
不好奇编程站长 · B站「村长不好奇」

村长

很多人学 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 安全得多。

小结​

你的需求别再手写直接用
统计频次字典加 ifCounter
按键分组判断键是否存在defaultdict
先进先出队列list.pop(0)deque
重复计算自己存缓存字典lru_cache
嵌套循环拍平双层 foritertools
拼文件路径字符串加斜杠pathlib
基础统计手写求和statistics
忽略某个异常try/except/passcontextlib.suppress

村长划重点:标准库的判断标准很简单——你正准备写的那段「通用工具代码」,十有八九已经有人写好了。动手前先搜一下官方文档,比写完再重构省时间。

这 8 个只是冰山一角。想系统过一遍标准库,可以接着看我们教程里的「标准库速览(一)(二)」两章。

我是村长,下篇聊聊怎么少写 print、早点下班。