04_迭代器装饰器与标准库实践
生成器、装饰器、pathlib、JSON、日志与可测试的命令行工具。
Python 迭代器、装饰器与标准库实践
学习目标:理解惰性数据流和函数包装,并用标准库写一个可测试的小工具,不依赖重型框架。
1. 迭代器与生成器
可迭代对象可用 for 遍历;迭代器通过 __next__ 逐步产生值。生成器函数包含 yield,调用时返回生成器对象,只有迭代到该位置才执行函数体。它适合逐行处理大文件,但不会自动解决所有内存问题:如果最后又 list(generator),仍会把全部结果放进内存。
from collections.abc import Iterator
from pathlib import Path
def nonempty_lines(path: Path) -> Iterator[str]:
with path.open(encoding="utf-8") as stream:
for line in stream:
text = line.strip()
if text:
yield text
文件在生成器遍历期间保持打开;若调用方提前停止遍历,生成器关闭时才会退出 with。需要更严格的资源生命周期时,让调用方管理文件或显式关闭生成器。不要把生成器返回给已经结束的数据库会话之外使用。
2. 装饰器包装横切行为
装饰器接收函数并返回一个新函数,适合对日志、计时、权限等重复行为做明确包装。functools.wraps 保留被包装函数的名称与文档信息。
from functools import wraps
from time import perf_counter
def timed(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
started = perf_counter()
try:
return fn(*args, **kwargs)
finally:
print(f"{fn.__name__}: {perf_counter() - started:.3f}s")
return wrapper
该示例适合同步函数,不能直接拿来准确计量 async def 的实际执行时间:它调用协程函数只会得到协程对象。生产日志应使用日志库,并避免记录敏感参数。装饰器层数过多会使调用路径难追踪,优先保持行为显式。
3. pathlib、json 与日志
pathlib.Path 比手工拼接路径更清楚;json.loads 可解析 JSON,但解析后仍需检查数据形状。应用日志使用 logging,记录操作、请求 ID 和安全的错误类别,不把密码、令牌或完整私人文件写入日志。
import json
from pathlib import Path
def read_settings(path: Path) -> dict[str, str]:
raw: object = json.loads(path.read_text(encoding="utf-8"))
if not isinstance(raw, dict):
raise ValueError("settings must be an object")
if any(not isinstance(k, str) or not isinstance(v, str) for k, v in raw.items()):
raise ValueError("settings must map strings to strings")
return raw
静态类型标注不能保证 JSON 运行时形状,因此这里逐项检查。配置来源若是环境变量,也要在应用启动时集中解析并对缺失值给出明确错误。
4. 包、依赖和命令行
按功能分包,避免脚本之间互相用相对工作目录读取文件。命令行工具可用 argparse 声明参数;项目依赖、Python 版本和测试命令写在 pyproject.toml 与 README 中。pip 安装包之前确认当前虚拟环境和解释器路径,减少“装了包却导入不到”的问题。
练习:做一个 notes stats <path> 命令,逐行统计非空行数与关键词出现次数;用临时文件测试空文件、中文和非法 JSON 配置。