← Backend / Python

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 配置。