《Python编程入门》14.3 覆盖率、ruff/mypy 与 CI 门禁
从 pytest-cov 的实跑报告讲起,说明 100% 行覆盖不等于测到边界,用 --cov-branch 对比数字;再实跑 ruff check/format 与 mypy --strict,给出 pyproject 配置段,最后落到 pre-commit 与 GitHub Actions,把质量门禁分成本地、CI、发布前三层。
category
从 pytest-cov 的实跑报告讲起,说明 100% 行覆盖不等于测到边界,用 --cov-branch 对比数字;再实跑 ruff check/format 与 mypy --strict,给出 pyproject 配置段,最后落到 pre-commit 与 GitHub Actions,把质量门禁分成本地、CI、发布前三层。
把测试从单条函数升级为可复用、可组合的工程:讲清 fixture 的参数注入与四种作用域、yield 拆解、conftest.py 层级发现、tmp_path 与 monkeypatch,再用 parametrize 和 unittest.mock 演示 patch 该打在哪一层、autospec 与 call_args 怎么用。
从 unittest 的写法对比切入 pytest:讲清 test_ 前缀的自动发现规则、assert 断言重写为何能给出逐元素 diff、pytest.raises 与 match、pytest.approx 浮点比较、skip/skipif/xfail 三种标记,以及 src 布局、pyproject 配置与 -v/-k/-x/--lf 常用开关。
同步库直接写进 async 会阻塞整个事件循环。本节用本地服务实测 requests 串行、to_thread 与 httpx.AsyncClient 的耗时差,讲连接池、contextvars、async for 与 async with,并盘点假异步:忘记 await、循环里串行 await、create_task 丢引用,最后说明何时不该用异步。
create_task 立刻调度、gather 要 await 才跑。本节对比 gather 与 TaskGroup 的错误处理:gather 不取消兄弟任务,TaskGroup 会取消并用 ExceptionGroup 汇总;再讲 asyncio.timeout、shield、CancelledError 的 BaseException 语义、Queue、Semaphore 与 Runner。
协程函数调用后只返回一个协程对象,不 await 就静默不执行。本节从 async def 与 await 的语义讲起,用真实计时证明单线程并发,再用 time.sleep 与 asyncio.sleep 的对比说明阻塞调用如何卡死事件循环,并厘清 get_running_loop 与 get_event_loop 的差异。
从 TCP/IP 分层讲起,用 socket 手写能跑通的 echo server/client,理解 bind/listen/accept/connect/send/recv、粘包与分帧、settimeout;再用原始 socket 看清 HTTP 请求行长什么样,对比 urllib、requests 会话复用与 httpx 同步/异步双接口。
线程池与进程池共享同一套 concurrent.futures 接口:submit 返回 Future、map 保序、as_completed 抢先返回,异常要调用 result() 才抛出。本节实测进程池的 __main__ 守卫报错、小任务反转、chunksize 与启动开销,并讲清进程间共享数据。
GIL 让同一时刻只有一个线程执行 Python 字节码。本节讲清它保护什么、为何存在,用真实计时证明 CPU 密集多线程不提速而 IO 密集近线性提速,再讲透 Thread/Lock/Event/Semaphore/Queue、竞态复现与线程进程选型。
sys.argv 太原始,argparse 是标准库自带的命令行解析方案。本节覆盖位置与可选参数、type 与 choices、nargs、store_true/count/append、子命令、FileType、parse_known_args 与退出码,并给出 __main__ 守卫组织方式,每个示例都真实运行。
print 无法分级、无法定向、无法关闭。本节讲清 logging 的 Logger/Handler/Formatter/Filter 架构、层级传播与 basicConfig 的局限,用 dictConfig 写完整配置,示范 % 延迟格式化、exc_info 异常日志、自定义 JSON Formatter 与日志轮转。
datetime 与时区是 Python 最易踩坑的标准库之一。本节从时间戳讲起,理清 naive 与 aware 的区别,实测 utcnow() 的弃用警告,用 zoneinfo 演示 Asia/Shanghai 与 America/New_York 的夏令时边界,并对比 replace 与 astimezone 的经典陷阱、ISO 8601 与 timedelta。
json.dumps/loads 的 ensure_ascii、indent、sort_keys、default 参数与中文坑,csv 的 DictReader/DictWriter、newline 的必要性与 Sniffer,3.11+ 只读的 tomllib,以及序列化安全:pickle 为何不能反序列化不可信数据、yaml.safe_load 与 JSON 的边界。
从 open() 的模式与 encoding 参数讲起,说清为什么必须显式指定编码;对比 read()、readlines() 与逐行迭代的内存差异,用 pathlib.Path 做现代路径操作,覆盖 glob/rglob/iterdir/walk、__file__ 定位、UTF-8 BOM 与 GBK 乱码排查,以及 tempfile 配合 os.replace 的原子写入。
字符串方法全景、切片与不可变、三种格式化的取舍与 f-string 调试写法、encode/decode 的编码错误处理,再到 re 模块的 match/search/fullmatch 区别、分组与命名分组、findall 与 finditer、贪婪与非贪婪、sub 回引,以及预编译缓存与一条日志解析实战。
静态类型检查器只能看代码,看不到运行时流入的 JSON、环境变量与用户输入。本节先划清静态类型的边界,再入门 Pydantic V2:用 BaseModel 定义模型、Field 加约束、model_validate / model_dump 序列化、field_validator / model_validator 写校验,并用 TypeAdapter 处理裸类型。
当同一段逻辑要处理多种类型时就需要泛型。本节从 TypeVar 与 bound 讲起,给出泛型函数与泛型类的写法,对照 3.12 的 PEP 695 新语法如何简化 type X = ...、class C[T] 与 def f[T];随后深入 Protocol 子类型、TypedDict、NamedTuple、@overload、Self 与 3.13 的 TypeIs,并讲清变型的直觉。
类型注解是 Python 静态分析的地基,运行时却几乎不做任何检查。本节从「写了 int 传 str 也能跑」讲起,梳理 Optional、Union、内置泛型、Literal、Final、Annotated 等语法,厘清 Any 与 object 的边界,再用 pyright 与 mypy --strict 在真实文件上跑出检查结果,并说明注解在运行时如何被读取、3.14 起为何默认延迟求值。
装饰器本质是函数到函数的高阶函数,@deco 只是 f = deco(f) 的语法糖。本节从最朴素的例子讲起,强调必须用 functools.wraps 保留元信息;再讲带参数的装饰器、类装饰器与实例方法装饰器的差异、缓存装饰器的实现思路与可哈希约束、singledispatch 的类型分派,以及多个装饰器的执行顺序,最后落到鉴权、重试、计时、注册表等真实工程场景。
yield 不只是用来吐出值的。本节从 send() 讲起,说明生成器为什么被称为协程的雏形;再讲 throw() 与 close() 的清理语义、GeneratorExit 为何必须重新抛出、yield from 的委托规则以及它如何取出子生成器的返回值;最后用生成器搭一条读文件、过滤、转换、聚合的惰性管道,说清它比每步生成一个列表省在哪里,并点一句异步生成器为何是另一套东西。