别说,你是不是也有这种习惯:写 Python 的时候,第一反应是 “我自己写一个工具函数”,而不是想想“标准库里其实早就有现成的了”。
我以前也这样,直到有一天翻项目代码,发现自己写了三个版本的“递归遍历文件夹”,两个版本的“重试装饰器”,还有一堆“自己拼 URL、自己算时间差”的乱七八糟小函数,看着都嫌烦。
所以今天就按着“别再重复造轮子”这个思路,聊聊 Python 标准库里 10 个真的很能打的模块,全都自带,零依赖,直接 import 就能用。你以后再写这些功能,就先问一句:这玩意 Python 自己会不会?
日常那些什么“创建文件夹、删文件、看环境变量、切换目录”,很多人第一反应就是自己拼路径、自己判断。
其实绝大部分就是两行 os 搞定。
import os
# 环境变量
db_url = os.getenv("DB_URL", "sqlite:///default.db")
# 确保目录存在
log_dir = "logs"
os.makedirs(log_dir, exist_ok=True)
# 遍历文件
for root, dirs, files in os.walk("."):
for name in files:
if name.endswith(".py"):
print(os.path.join(root, name))
os.makedirs(..., exist_ok=True) 这种小细节就挺省心的:目录存在也不报错,少了很多 if 判断。
os.path 那一堆函数确实能用,不过一多就显得乱。pathlib 把路径变成对象,用起来顺手很多。
from pathlib import Path
base = Path(__file__).parent
data_dir = base / "data"
data_dir.mkdir(exist_ok=True)
log_file = data_dir / "app.log"
# 判断 / 读取 / 写入
if log_file.exists():
print(log_file.read_text(encoding="utf-8"))
log_file.write_text("hello, pathlib\n", encoding="utf-8")
你看 (base / "data" / "app.log") 这种写法,比 "{}{}{}".format(...) 或 os.path.join 好读太多,还自动帮你处理斜杠。
很多人做统计的时候还在:
d = {}
for x in data:
if x notin d:
d[x] = 0
d[x] += 1
标准库直接给了一个 Counter,就是专门干这个的。
from collections import Counter, defaultdict
words = ["apple", "banana", "apple", "orange", "banana", "apple"]
counter = Counter(words)
print(counter.most_common(2)) # [('apple', 3), ('banana', 2)]
# 默认字典:自动创建初始值
index = defaultdict(list)
for i, word in enumerate(words):
index[word].append(i)
print(index["apple"]) # [0, 2, 5]
defaultdict(list) 这种模式,在做分组、索引、聚合的时候特别香,很多人是自己 if key not in d: d[key] = [] 那样写,其实都不用。
只要你开始写“嵌套循环”“组合排列”“滑窗”这类东西,其实都可以先想一下 itertools 有没有帮你造好轮子。
import itertools as it
nums = [1, 2, 3, 4]
# 所有两两组合(无顺序)
for a, b in it.combinations(nums, 2):
print(a, b)
# 笛卡尔积
colors = ["red", "blue"]
sizes = ["S", "M", "L"]
for c, s in it.product(colors, sizes):
print(c, s)
# 累积求和
for s in it.accumulate(nums):
print(s)
很多代码里那种三四层 for 循环,其实用 product 一行就描述清楚了,逻辑也更不容易写错。
这个模块不显眼,但是把几个常用轮子都帮你做好了,比如重试前的“缓存结果”、给函数预先绑定部分参数、做排序 key。
最常见的就是 lru_cache,你要是写递归、写各种查配置的函数,先套一层缓存,性能立马上去。
from functools import lru_cache, partial
@lru_cache(maxsize=128)
deffib(n: int) -> int:
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
print(fib(40)) # 有缓存就很快
# 偏函数:给函数预先填好部分参数
int2 = partial(int, base=2)
print(int2("1010")) # 10
很多人为了“缓存结果”自己搞一个全局 dict,还要考虑线程安全,其实多数场景下 lru_cache 就够用了。
时间处理这种东西,手写字符串拼接和时间戳运算,非常容易踩坑,尤其是时区。
from datetime import datetime, timedelta, timezone
# 当前时间
now = datetime.now()
# 转成字符串
s = now.strftime("%Y-%m-%d %H:%M:%S")
print("现在:", s)
# 字符串转时间
dt = datetime.strptime("2025-01-01 12:30:00", "%Y-%m-%d %H:%M:%S")
# 计算时间差
delta = now - dt
print("相差天数:", delta.days)
# 带时区的时间
tz = timezone(timedelta(hours=8)) # 东八区
now_cn = datetime.now(tz=tz)
print("东八区时间:", now_cn.isoformat())
只要场景一复杂(跨天、跨时区、时间间隔),就别用“手写秒数”那种方式了,用 datetime 清清楚楚。
以前喜欢自己 split("=") 解析配置、或者自己拼字符串,现在基本上都是直接用 JSON,一是标准,二是跟前端、其他语言配合方便。
import json
from pathlib import Path
config_path = Path("config.json")
# 写配置
config = {
"host": "127.0.0.1",
"port": 8080,
"debug": True,
"features": ["cache", "metrics"]
}
config_path.write_text(json.dumps(config, indent=2, ensure_ascii=False), encoding="utf-8")
# 读配置
raw = config_path.read_text(encoding="utf-8")
loaded = json.loads(raw)
print(loaded["host"], loaded["port"])
indent、ensure_ascii 这些参数好好用一下,配置文件瞬间清爽可读。
刚上手的时候随便 print 一下没什么问题,项目一大,排查问题全靠肉眼搜 print,那就挺折磨的了。 标准库自带 logging,够用很久。
import logging
from pathlib import Path
log_file = Path("app.log")
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s - %(message)s",
handlers=[
logging.FileHandler(log_file, encoding="utf-8"),
logging.StreamHandler()
]
)
logger = logging.getLogger("demo")
logger.info("服务启动了")
logger.warning("这个配置有点奇怪:%s", {"debug": True})
logger.error("出错了", exc_info=True)
有日志级别、有时间、有文件,后面再接日志平台、再过滤信息都方便,比到处 print("here") 要靠谱太多。
很多人写脚本,参数全是写死在文件里,要改个路径还得打开代码改一遍。 稍微正规一点的做法:用 argparse 做命令行参数,脚本瞬间像个真正的“小工具”。
import argparse
from pathlib import Path
defmain():
parser = argparse.ArgumentParser(description="简单的文件统计工具")
parser.add_argument("path", help="要统计的目录")
parser.add_argument("--ext", default=".py", help="只统计某种后缀的文件")
args = parser.parse_args()
base = Path(args.path)
count = 0
for p in base.rglob(f"*{args.ext}"):
if p.is_file():
count += 1
print(f"{base} 下共有 {count} 个 {args.ext} 文件")
if __name__ == "__main__":
main()
以后用的时候就变成:
python count_files.py . --ext .txt
脚本一旦这样写,多给别人用几次,就会下意识地继续沿用这种模式。
你要是自己去写 threading.Thread / multiprocessing.Process,加队列、加锁,写着写着心情就复杂了。 很多“简单的并发任务”,比如批量请求、批量 IO 操作,其实用 concurrent.futures 的线程池就够了。
from concurrent.futures import ThreadPoolExecutor, as_completed
import time
import random
deffetch(url: str) -> tuple[str, float]:
# 模拟网络请求
cost = random.uniform(0.1, 0.5)
time.sleep(cost)
return url, cost
urls = [f"https://example.com/{i}"for i in range(10)]
with ThreadPoolExecutor(max_workers=5) as executor:
future_map = {executor.submit(fetch, url): url for url in urls}
for future in as_completed(future_map):
url, cost = future.result()
print(f"{url} 用时 {cost:.3f}s")
很多“我要写个简单爬虫”“我要并发调几个接口”的场景,用这个比自己手写线程管理省心太多。
最后随便唠一句,其实 Python 标准库里能干活的东西远不止这 10 个,像 re(正则)、subprocess(调外部命令)、typing / dataclasses(结构化数据)、sqlite3(轻量数据库)也都很能打。
但如果你把上面这 10 个模块先用顺手了,你会慢慢发现: 很多“小轮子”根本不需要你造,Python 官方已经帮你打磨好了一套工具箱,你要做的,就是记得去翻它。
下次写脚本的时候,不妨先停一秒,问自己一句: “这个功能,是不是已经有现成模块了?”
-END-
我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html