前言
同样是标准库模块,真正的差别不在名字认不认识,而在参数怎么写、边界怎么处理、异常往哪抛 。比如 json.dumps 的 ensure_ascii 默认是 True;csv 在 Windows 上写文件不加 newline="" 就多空行;subprocess.run 默认不检查子进程的返回码。这些都是文档里写着、但第一次用绝不会注意到的细节。
本文挑七个最常用的标准库模块,把关键函数签名、返回类型和易错边界逐条过一遍。所有示例只依赖标准库,Python 3.8 及以上可运行;涉及更新版本才有的接口会单独标注。
需要先更正一个常见说法:「常见模块」不等于「必须全部掌握」。真正的高频面很窄------文件、JSON、时间、正则、计数、日志、命令行,覆盖日常脚本九成以上的需求。剩下的按需再学。
一、pathlib:用对象表示路径
pathlib.Path 把路径封装成对象,拼接用 / 运算符,比字符串相加安全(自动处理分隔符)。
python
# 适用于 Python 3.8+
from pathlib import Path
root = Path("data")
target = root / "reports" / "2024.txt"
target.parent.mkdir(parents=True, exist_ok=True) # 递归建目录
target.write_text("hello\n", encoding="utf-8")
print(target.read_text(encoding="utf-8")) # hello
print(target.name, target.stem, target.suffix) # 2024.txt 2024 .txt
关键方法一览:
| 方法 | 签名要点 | 返回 |
|---|
|-------------|-------------------------------------------------------|-------|
| read_text | read_text(encoding=None, errors=None, newline=None) | str |
|--------------|--------------------------------------------------------------|--------|
| write_text | write_text(data, encoding=None, errors=None, newline=None) | 写入的字符数 |
|--------------|-----|---------|
| read_bytes | 无参数 | bytes |
|---------|----------------------------------------------------|--------|
| mkdir | mkdir(mode=0o777, parents=False, exist_ok=False) | None |
|--------|-----------------------------------------------------------------|-----|
| glob | glob(pattern, *, case_sensitive=None, recurse_symlinks=False) | 生成器 |
|---------|---------------------------------------------------|-----|
| rglob | rglob(pattern, ...),等价于 glob("**/" + pattern) | 生成器 |
三点提醒。第一,read_text 的 encoding 默认是 None,表示用平台默认编码------Windows 上通常不是 UTF-8,跨平台读中文文件几乎必乱码,总是显式写 encoding="utf-8" 。第二,Path 的实例方法 glob 返回的是生成器 ,要列表得自己 list()。第三,mkdir 默认 parents=False,父目录不存在会抛 FileNotFoundError;递归创建要传 parents=True。
rglob 的 **/ 能匹配零层目录,所以它会把当前目录下的文件也算进去,不只是子目录里的。
python
# 适用于 Python 3.8+
from pathlib import Path
for p in sorted(Path(".").rglob("*.py"))[:3]:
print(p, p.stat().st_size)
二、json 与 csv:结构化数据的两条路
json 模块四个函数,名字里的 s 表示 string,没有 s 表示操作文件对象:
| 函数 | 作用 | 返回 |
|---|
|------------------------|--------------|-------|
| json.dumps(obj, ...) | 对象转 JSON 字符串 | str |
|-----------------|-------------|----------|
| json.loads(s) | JSON 字符串转对象 | object |
|---------------------------|----------|--------|
| json.dump(obj, fp, ...) | 对象写入文件对象 | None |
|-----------------|-----------|----------|
| json.load(fp) | 从文件对象读出对象 | object |
python
# 适用于 Python 3.8+
import json
payload = {"name": "张三", "scores": [90, 85], "active": True}
text = json.dumps(payload, ensure_ascii=False, indent=2)
print(text)
print(json.loads(text)["scores"][0]) # 90
ensure_ascii=False 控制是否把非 ASCII 字符转成 \uXXXX 转义。默认 True,中文会被转义成一串码点,虽然解析结果一样,但文件基本没法读。indent=2 用来生成方便人看的缩进格式。
一个必须知道的边界:JSON 的键只能是字符串 。把 {1: "a"} 传给 json.dumps,默认会抛 TypeError(skipkeys=True 可改成跳过)。反过来,json.loads 得到的对象里,数字键永远不存在------因为它们原本就是字符串。
csv 模块处理逗号分隔文本。读文件建议显式传 encoding;写文件必须传 newline="":
python
# 适用于 Python 3.8+
import csv
rows = [["名字", "分数"], ["张三", "90"], ["李四", "85"]]
with open("scores.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(rows[0])
writer.writerows(rows[1:])
with open("scores.csv", newline="", encoding="utf-8") as f:
for row in csv.DictReader(f):
print(row["名字"], row["分数"])
newline="" 的作用是禁止 Python 再做换行转换 ,由 csv 模块自己控制行尾。省掉它,Windows 上每行之间会多出一个空行。csv.DictReader 把首行当表头,后续行以字典形式给出;对应的 csv.DictWriter 需要先用 fieldnames 参数指定列。
三、容器、统计与日期时间
collections 与 statistics:容器与统计
collections.Counter 做计数,most_common(n=None) 返回按次数降序的列表:
python
# 适用于 Python 3.8+
from collections import Counter, deque, namedtuple
words = "the quick the lazy the".split()
c = Counter(words)
print(c["the"]) # 3
print(c.most_common(1)) # [('the', 3)]
print(c["missing"]) # 0,不存在的键返回 0 而不是 KeyError
注意最后一行:Counter 访问不存在的键返回 0 而不抛异常 ,这和普通 dict 的 [] 行为不同。这很方便(省掉判断),也很危险(拼错键名不会报错,只是静默得到 0)。
namedtuple 生成带字段名的元组类,访问用属性而不是下标:
python
# 适用于 Python 3.8+
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
print(p.x, p[0], tuple(p)) # 3 3 (3, 4)
statistics 模块提供基础统计量,函数都很短:
| 函数 | 签名 | 说明 |
|---|
|--------|--------------|------|
| mean | mean(data) | 算术平均 |
|---------|-----------------------------|-------------|
| fmean | fmean(data, weights=None) | 用浮点累加,适合大样本 |
|----------|----------------|-----|
| median | median(data) | 中位数 |
|--------|--------------|------------|
| mode | mode(data) | 众数;无重复值时报错 |
|---------|--------------------------|---------------|
| stdev | stdev(data, xbar=None) | 样本标准差(分母 n-1) |
|----------|-------------------------|-------------|
| pstdev | pstdev(data, mu=None) | 总体标准差(分母 n) |
python
# 适用于 Python 3.8+
import statistics
data = [2, 4, 4, 4, 5, 5, 7, 9]
print(statistics.mean(data)) # 5
print(statistics.median(data)) # 4.5
print(statistics.mode(data)) # 4
stdev 与 pstdev 的区别值得记住:前者是样本 标准差,后者是总体标准差,分母不同,结果在小样本下差别明显。选错不会报错,只会算出一个「看起来对」的数。
datetime:日期时间的三件事
datetime 模块的核心类型是 date、time、datetime 和 timedelta。
python
# 适用于 Python 3.8+
from datetime import datetime, timedelta
now = datetime.now()
print(now.strftime("%Y-%m-%d %H:%M:%S")) # 2026-10-07 12:00:00 格式
later = now + timedelta(days=7, hours=3)
print(later > now) # True
print((later - now).total_seconds()) # 608400.0
timedelta 的构造参数是 days、seconds、microseconds、milliseconds、minutes、hours、weeks,全部是关键字参数 ,写 timedelta(7, 3) 会得到 7 天 3 秒而不是 7 天 3 小时------参数是按顺序填 days, seconds 的,这个错误非常常见。
解析字符串用 datetime.strptime(date_string, format),格式化用实例方法 dt.strftime(format)。常用格式符:
| 格式符 | 含义 | 示例输出 |
|---|
|------|------|------|
| %Y | 四位年份 | 2026 |
|------|------|----|
| %m | 两位月份 | 10 |
|------|-----|----|
| %d | 两位日 | 07 |
|----------------|---------------|----------|
| %H %M %S | 时、分、秒(24 小时制) | 12 00 00 |
|------|------------|--------|
| %f | 微秒,补零到 6 位 | 000000 |
|------|--------|-------|
| %z | UTC 偏移 | +0800 |
|------|--------------|--------|
| %A | 星期全名(依赖区域设置) | Monday |
|------|---------|-----|
| %j | 一年中的第几天 | 280 |
要注意 %Y 和 %y 不是一回事 :%y 是两位年份,解析时会有世纪推断问题。另外从 3.13 起,只含「日」不含「年」的格式串调用 strptime 会发出 DeprecationWarning ,官方计划在后续版本把它变成错误或改默认年。从 3.12 起 utcnow() 与 utcfromtimestamp() 已弃用,应改用带时区参数的写法。
naive 与 aware 的区分必须提一句:没有 tzinfo 的 datetime 叫 naive,有 tzinfo 的叫 aware,两者相减会抛 TypeError 。带时区用 zoneinfo 模块,它是 Python 3.9 新增的。
四、文本、校验、命令行与日志
re 与 hashlib:文本与校验
re 的常用入口是四个函数和一个类方法:
python
# 适用于 Python 3.8+
import re
text = "订单号 A123,订单号 B456"
print(re.findall(r"[A-Z]\d{3}", text)) # ['A123', 'B456']
print(re.search(r"\d+", text).group()) # 123
print(re.sub(r"\d", "#", text)) # 订单号 A###,订单号 B###
三条实践建议。第一,正则一律用原始字符串 r"...",否则 \d、\n 会先被字符串字面量解释一遍。第二,re.search 返回的是 Match 对象或 None,必须先判空 再取 .group(),否则 None.group() 直接 AttributeError。第三,同一个正则重复使用时用 re.compile(pattern) 编译成对象,代码更清晰。
hashlib 做摘要(哈希):
python
# 适用于 Python 3.8+
import hashlib
digest = hashlib.sha256(b"hello").hexdigest()
print(len(digest), digest[:16]) # 64 2cf24dba5fb0a30e
hexdigest() 返回十六进制字符串(64 个字符),digest() 返回原始字节。要更新已有摘要对象用 .update(data),参数必须是 bytes,不是 str------传字符串会抛 TypeError。
算大文件的摘要时不要一次性读进内存。Python 3.11 起提供了 hashlib.file_digest(fileobj, digest, /):
python
# 适用于 Python 3.11+;3.11 以下需手动分块读出后 update
import hashlib
with open("big.bin", "rb") as f:
print(hashlib.file_digest(f, "sha256").hexdigest())
必须强调:哈希不是加密。 SHA-256 这类摘要算法是单向的、不可逆的,适合做完整性校验;存密码要用专门的口令派生函数 ,比如 hashlib.scrypt,而不是直接对密码哈希一次。不过 sha256 也是 hashlib.new 里名字最常用的那个,别把它当加密算法描述。
argparse 与 logging:程序的两个门面
argparse 用来解析命令行参数:
python
# 适用于 Python 3.8+
import argparse
def build_parser():
parser = argparse.ArgumentParser(description="示例工具")
parser.add_argument("path", help="要处理的文件路径")
parser.add_argument("--times", type=int, default=1, help="重复次数")
parser.add_argument("--verbose", action="store_true", help="输出详细信息")
return parser
def main():
args = build_parser().parse_args()
if args.verbose:
print(f"路径={args.path} 次数={args.times}")
if __name__ == "__main__":
main()
add_argument 里没有前缀的 "path" 是位置参数 (必填),带 -- 的是可选参数 。type=int 会在解析时做转换------这是 argparse 比手工读 sys.argv 强的地方。action="store_true" 表示出现即为真,不需要跟值。
logging 比到处 print 更适合真实程序:
python
# 适用于 Python 3.8+
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(name)s %(message)s",
)
log = logging.getLogger(__name__)
log.info("开始处理")
log.warning("配置项缺失,使用默认值")
级别从低到高是 DEBUG、INFO、WARNING、ERROR、CRITICAL。basicConfig 只对「根记录器」生效,而且重复调用只有第一次起作用 ------如果你在多个模块里各写一次 basicConfig,后面那些会被静默忽略。这是排查「日志格式没变」问题时最容易忽略的一点。
另外,getLogger(__name__) 用模块名作记录器名,日志里就能看出每条来自哪个模块,这几乎是无成本的收益。
常见坑点
1. read_text 不写 encoding
❌ Path("a.txt").read_text() 在 Windows 上按默认编码(通常 GBK)读 UTF-8 文件,中文变乱码或直接 UnicodeDecodeError。 ✅ 显式写 read_text(encoding="utf-8")。
2. timedelta 参数顺序记错
❌ timedelta(7, 3) 以为是 7 天 3 小时,实际是 7 天 3 秒 。 ✅ 全部用关键字:timedelta(days=7, hours=3)。
3. mkdir 忘了 parents=True
❌ Path("a/b/c").mkdir() 在 a 不存在时抛 FileNotFoundError。 ✅ Path("a/b/c").mkdir(parents=True, exist_ok=True)。
4. re.search 结果不判空
❌ re.search(r"\d+", text).group(),没匹配到时对 None 调用 .group() 报 AttributeError。 ✅ 先 m = re.search(...),if m: 再取 m.group()。用 re.findall 时也不用判空,它返回空列表。
5. Counter 掩盖键名拼写错误
❌ 用 Counter 统计后写 c["frequncy"](拼错),拿到 0 而不是报错,统计结果静默出错。 ✅ 键名集中定义成常量;需要严格性时用 dict 或先 set(c.keys()) 校验。
6. csv 写入不加 newline=""
❌ open(path, "w", encoding="utf-8") 后直接写,Windows 上每行之间出现空行。 ✅ open(path, "w", newline="", encoding="utf-8")。
7. hashlib 传了字符串
❌ hashlib.sha256("hello") 抛 TypeError,因为摘要函数只接受字节串。 ✅ hashlib.sha256("hello".encode("utf-8")) 或直接写 b"hello"。
8. 在多个模块里重复调用 basicConfig
❌ 每个模块都写一次 logging.basicConfig(...),只有第一次生效,后面的格式设置被忽略。 ✅ 只在程序入口调用一次,其他模块用 logging.getLogger(__name__) 取记录器。
总结
| 模块 | 核心函数 | 最容易踩的边界 |
|---|
|-----------|--------------------------------------|--------------------|
| pathlib | read_text / write_text / mkdir | 编码与 parents=True |
|--------|-------------------|--------------------------|
| json | dumps / loads | ensure_ascii 默认 True |
|-------|-------------------------|-------------------------|
| csv | writer / DictReader | Windows 必须 newline="" |
|---------------|------------------------------------|------------------|
| collections | Counter / deque / namedtuple | Counter 缺键返回 0 |
|--------------|-----------------------------|-------------------------|
| statistics | mean / median / stdev | stdev 与 pstdev 分母不同 |
|------------|---------------------------------------|--------------------|
| datetime | strptime / strftime / timedelta | naive 与 aware 不能相减 |
|-----------|------------------------|--------|
| hashlib | sha256 / hexdigest | 只接受字节串 |
|-----------|-----------------------------|---------------------|
| logging | basicConfig / getLogger | basicConfig 只生效一次 |
这八个模块的共同点是:接口简单,边界刁钻 。参数少到几乎不用查文档就能写出来,但默认值的选择往往和直觉相反------ensure_ascii 默认转义中文、subprocess.run 默认不检查返回码、basicConfig 默认只生效一次。养成「用之前扫一眼签名和默认值」的习惯,比记住任何一个具体结论都有用。