🎯 学习目标
- 熟练使用 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 | 日 | %H | 24小时制时 |
%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 依然有效。