Python 语法篇 · 第14章

常用标准库精讲

Python"自带电池"中最值得掌握的一批模块:增强容器、迭代工具、日期时间、日志、命令行解析与并发入门。

🎯 学习目标

  • 熟练使用 collections 的 Counter、defaultdict、deque
  • 了解 itertools、functools 高频工具
  • 掌握 datetime 日期时间计算与格式化
  • 会用 logging 输出规范日志、用 argparse 写命令行工具
  • 了解 os/sys、math/statistics 常用接口
  • 理解 GIL,建立多线程/多进程/异步的初步认识

1collections:增强容器

Counter:计数器(NLP 第一名高频工具)

from collections import Counter

c = Counter("aabbc")            # Counter({'a':2,'b':2,'c':1})
c = Counter(["python", "ai", "python"])
c.most_common(2)                # [('python',2),('ai',1)]
c["x"]                          # 0  不存在的键直接返回 0(不报错)

c.update(["ai"])                # 继续累加
c.subtract(["python"])          # 相减
c.elements()                    # 展开为元素迭代器
# 词频统计、Top N、共现计数、BLEU 评分里都有它的身影

defaultdict:带默认值的字典

from collections import defaultdict

# 普通字典做"分组"时要判断键是否存在
pairs = [("水果", "苹果"), ("蔬菜", "白菜"), ("水果", "香蕉")]
groups = {}
for k, v in pairs:
    if k not in groups:
        groups[k] = []          # 烦人的初始化
    groups[k].append(v)

# defaultdict 版本:访问不存在的键时自动调用 list() 生成 []
groups2 = defaultdict(list)
for k, v in pairs:
    groups2[k].append(v)        # 无需判断!
print(dict(groups2))            # {'水果':['苹果','香蕉'],'蔬菜':['白菜']}

counts = defaultdict(int)       # 不存在默认 0 —— 词频统计神器
for w in ["a", "b", "a"]:
    counts[w] += 1

sets = defaultdict(set)         # 一键多值(值去重)

deque:双端队列

from collections import deque

dq = deque([1, 2, 3])
dq.append(4)         # 右侧入队
dq.appendleft(0)     # 左侧入队
dq.pop()             # 右侧出队
dq.popleft()         # 左侧出队 —— O(1)!list.pop(0) 是 O(n)
dq.extend([5, 6])
dq.rotate(2)         # 整体右移2位(环形旋转)
dq = deque(maxlen=3) # 固定长度,超出自动挤掉旧元素(滑动窗口天然支持)

其他成员

from collections import namedtuple, OrderedDict, ChainMap

Point = namedtuple("Point", ["x", "y"])   # 命名元组(第6章)
p = Point(1, 2); p.x

od = OrderedDict()        # 保序字典(Python3.7 起普通 dict 已保序,了解即可)
od["a"] = 1

env = ChainMap({"a": 1}, {"b": 2})       # 把多个字典当一个查(配置分层)

2itertools:迭代器工具箱

import itertools as it

# 无限迭代器
list(it.islice(it.count(10), 5))     # [10,11,12,13,14]
list(it.islice(it.cycle("AB"), 5))   # ['A','B','A','B','A']
list(it.repeat("x", 3))              # ['x','x','x']

# 拼接与展平
list(it.chain([1, 2], [3, 4], [5]))  # [1,2,3,4,5]
list(it.chain.from_iterable([[1,2],[3,[4]]]))  # [1,2,3,[4]] 只展一层

# 累积运算
list(it.accumulate([1, 2, 3, 4]))    # [1,3,6,10] 前缀和
import operator
list(it.accumulate([1,2,3,4], operator.mul))  # [1,2,6,24] 前缀积

# 笛卡尔积、排列、组合(枚举/测试数据)
list(it.product("AB", [1, 2]))       # [('A',1),('A',2),('B',1),('B',2)]
list(it.permutations("ABC", 2))      # 有序选2:AB AC BA BC CA CB
list(it(it.combinations("ABC", 2)))  # 无序选2:AB AC BC
list(it.combinations_with_replacement("AB", 2))  # AA AB BB

# 分组(需先按键排序)
data = [("A",1),("A",2),("B",3)]
for key, grp in it.groupby(data, key=lambda x: x[0]):
    print(key, list(grp))

3functools:函数式工具

from functools import lru_cache, partial, reduce, wraps

# lru_cache:给函数结果加缓存(第12章装饰器处已演示)
@lru_cache(maxsize=128)
def fib(n):
    return n if n < 2 else fib(n-1) + fib(n-2)

# partial:固定一部分参数,造新函数(柯里化)
int2 = partial(int, base=2)
int2("1010")               # 10

def log(level, msg):
    print(f"[{level}] {msg}")
info = partial(log, "INFO")
info("启动")               # [INFO] 启动

# reduce:序列累积成单值(第7章)
reduce(lambda a, b: a * b, [1, 2, 3, 4])   # 24

# wraps:装饰器标配(第12章)

4datetime:日期与时间

from datetime import datetime, date, timedelta

# 获取与创建
now = datetime.now()            # 当前本地时间(含时分秒)
today = date.today()            # 今天(只有日期)
d = date(2026, 10, 25)
dt = datetime(2026, 10, 25, 9, 30, 0)

# 取分量
now.year; now.month; now.day
now.hour; now.minute; now.second
now.weekday()                   # 周一=0 … 周日=6

# 时间差:timedelta(可加减、乘除)
tomorrow = today + timedelta(days=1)
last_week = today - timedelta(weeks=1)
deadline = now + timedelta(hours=72)
delta = deadline - now          # 两个 datetime 相减得 timedelta
delta.days; delta.seconds

# 格式化:对象 → 字符串
now.strftime("%Y-%m-%d %H:%M:%S")   # '2026-10-25 09:00:00'
now.strftime("%Y年%m月%d日")
# 解析:字符串 → 对象
datetime.strptime("2026-10-25", "%Y-%m-%d")

# ISO 格式
datetime.fromisoformat("2026-10-25T09:30:00")
now.isoformat()

# 时间戳互转
datetime.fromtimestamp(1792886400)
now.timestamp()
占位含义占位含义
%Y四位年%m月(补零)
%d日%H24小时制时
%M分%S秒
%A星期全称%j一年中第几天

5math / statistics / random

import math, statistics, random

math.pi; math.e
math.ceil(2.1); math.floor(2.9)   # 3 / 2
math.trunc(2.9)                   # 2 截断取整
math.sqrt(16); math.log2(1024); math.log(math.e)
math.gcd(12, 18)                  # 6 最大公约数
math.comb(5, 2)                   # 10 组合数 C(5,2)
math.isclose(0.1+0.2, 0.3)        # True 浮点近似比较

data = [2, 4, 4, 4, 5, 5, 7, 9]
statistics.mean(data)             # 5.0 平均
statistics.median(data)           # 4.5 中位数
statistics.mode(data)             # 4 众数
statistics.pstdev(data)           # 总体标准差

random.seed(42)                   # 固定种子 → 结果可复现(实验必备)
random.randint(1, 100)
random.uniform(0, 1)              # 0~1 浮点
random.choice(["a", "b", "c"])
random.sample(range(1, 36), 5)    # 不放回抽5个(大乐透)
random.shuffle(data)              # 原地打乱(数据集划分时)

6os / sys / platform

import os, sys, platform

# os:环境与进程
os.getcwd()                       # 当前工作目录
os.environ.get("PATH")            # 读环境变量
os.environ["MY_VAR"] = "x"        # 设置(仅当前进程)
os.listdir(".")                   # 列目录(更推荐 pathlib)
os.system("echo hi")              # 执行系统命令(简单场景)
os.getpid()                       # 当前进程号

# sys:解释器信息
sys.version                       # Python 版本字符串
sys.path                          # 模块搜索路径(第8章)
sys.argv                          # 命令行参数列表(argv[0] 是脚本名)
sys.platform                      # 'win32' / 'darwin' / 'linux'
sys.exit(0)                       # 退出程序

platform.system()                 # 'Windows' / 'Darwin' / 'Linux'
platform.machine()                # 'AMD64' 等

7logging 日志速查

import logging

logger = logging.getLogger("nlp_app")
logger.setLevel(logging.DEBUG)

# 控制台处理器 + 格式
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter(
    "%(asctime)s | %(levelname)-7s | %(name)s | %(message)s",
    datefmt="%H:%M:%S"))
logger.addHandler(handler)

logger.debug("调试细节")       # 训练每个 batch 的 loss
logger.info("流程信息")        # 开始训练、保存模型
logger.warning("警告")         # 语料中有空文档
logger.error("错误")           # 单条数据处理失败
logger.exception("异常堆栈")   # except 块中使用,自动带 traceback

# 日志分级:DEBUG < INFO < WARNING < ERROR < CRITICAL
# 生产环境通常设 INFO,排查问题临时调 DEBUG;文件记录见第10章

8argparse:写命令行工具

# ===== word_count.py =====
import argparse

parser = argparse.ArgumentParser(description="简单的文本词频统计工具")
parser.add_argument("file", help="输入文本文件路径")                 # 位置参数
parser.add_argument("-n", "--top", type=int, default=10,
                    help="输出前 N 个高频词(默认10)")
parser.add_argument("--encoding", default="utf-8", help="文件编码")
parser.add_argument("-v", "--verbose", action="store_true",
                    help="是否输出详细信息")

args = parser.parse_args()
print("文件:", args.file)
print("Top:", args.top)
print("详细模式:", args.verbose)
python word_count.py news.txt -n 20 -v
python word_count.py --help          # argparse 自动生成帮助文档

9并发编程入门(建立概念)

GIL:先理解 Python 的"先天限制"

CPython 有全局解释器锁(GIL):同一时刻一个进程内只有一个线程能执行 Python 字节码。所以多线程不能加速 CPU 密集任务,但能加速 IO 密集任务(等待网络/磁盘时会释放 GIL)。

方案适合任务特点
threading 多线程IO 密集:下载、接口调用轻量,但受 GIL 限制不适合算
multiprocessing 多进程CPU 密集:大量计算真正并行,但开销大、通信成本高
asyncio 异步协程高并发 IO:爬虫、Web 服务单线程事件循环,代码要配合 await
concurrent.futures两种池的统一高层接口ThreadPoolExecutor / ProcessPoolExecutor
# 多线程示例:并发请求多个网页(IO 密集典型)
import concurrent.futures, time

def fetch(i):
    time.sleep(0.5)              # 模拟网络等待
    return f"结果{i}"

# 串行要 5 秒,线程池并发约 0.5 秒
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as pool:
    for result in pool.map(fetch, range(5)):
        print(result)
# asyncio 异步示例(3.7+)
import asyncio

async def fetch(i):
    await asyncio.sleep(0.5)     # await:等待时让出控制权
    return f"结果{i}"

async def main():
    results = await asyncio.gather(*(fetch(i) for i in range(5)))
    print(results)

asyncio.run(main())              # 约 0.5 秒完成 5 个等待

NLP 工程经验:批量请求大模型 API / 爬数据用 asyncio 或线程池;本地大规模数值计算交给 NumPy/sklearn/torch(底层 C/CUDA 已释放 GIL);真要并行处理语料用 ProcessPoolExecutor。

10语法篇收官与练习

📌 关键结论

  • 词频 Counter、分组 defaultdict、队列 deque 是标准库三件宝。
  • 日期计算用 timedelta,格式化 strftime / 解析 strptime。
  • 程序输出用 logging 分级日志;命令行工具用 argparse。
  • IO 密集选多线程/异步,CPU 密集选多进程或交给 C 扩展库。
  • 至此 Python 核心语法全部学完,接下来可以进入 NLP 应用篇实战!
练习1:用 defaultdict 把单词列表 ["apple","ant","banana","book"] 按首字母分组。
g = defaultdict(list) for w in words: g[w[0]].append(w) 得到 {'a':['apple','ant'],'b':['banana','book']}。无需先判断首字母键是否存在。
练习2:计算"100 天后是几月几号星期几",并输出为 "2027年02月02日 周二" 风格。
d = date.today() + timedelta(days=100) week = "周一周二周三周四周五周六周日"[d.weekday()*2:d.weekday()*2+2] print(d.strftime("%Y年%m月%d日"), week) 利用 timedelta 做日期加法、strftime 格式化、weekday 映射中文星期。
练习3:为什么 CPU 密集型 Python 程序用多线程几乎没有加速?应该怎么办?
GIL 使同一进程同一时刻只有一个线程执行 Python 字节码,CPU 计算时线程无法并行。可改用 multiprocessing 多进程(每进程独立解释器与 GIL),或使用 NumPy / PyTorch 等底层在 C/CUDA 中并行的库;IO 密集任务(网络等待)则多线程/asyncio 依然有效。