- STATUS.md:本次任务拆成「第一阶段已完成 / 第二阶段进行中」; L0 3/12;测试计数改为 39 passed / 44 skipped; 下一步里写明 io.py 待修的两处(mkdir 早于校验、缺 Raises) - docs/04-environment.md 坑 6:conda 升级留下的 *.conda_trash 残骸目录会被 Python 当成 namespace package 导入成功(__file__ 为 None), pandas 3.0 因此抛 ImportError: Can't determine version for zstandard 直接打断 pd.read_csv;含排查方法与「改名禁用」的修法 - docs/dev/personal-pitfalls.md:条目 3(注解与 docstring 打架)复发 +1 - 决策 26:未实现的算法逐条 skip,禁用模块级 pytestmark - 决策 27:zstandard 残骸改名禁用,不删不重装 - CHANGELOG:补 load_table/save_table 与 Fixed 段
69 lines
3.8 KiB
Markdown
69 lines
3.8 KiB
Markdown
# 个人易错点(只追加)
|
||
|
||
> **本文件的规矩**
|
||
> 1. **只追加。** 已有条目一个字都不改、不重排、不润色。发现写错了 → 在末尾补一条"更正",不要回头改。
|
||
> 2. **不通读。** 追加前只做两件事:`rg -i "<关键词>" docs/dev/personal-pitfalls.md` 看有没有记过;
|
||
> 看一眼下面的索引表最后一行拿编号。全文只在复盘时才读。
|
||
> 3. 一条写三行:**现象** / **为什么会错** / **以后怎么防**。
|
||
> 写不出"为什么会错"就先别记——那说明还没想清楚。
|
||
> 4. 记**规律**,不记事故。"今天又忘了"不新开条目,在已有条目下把 `复发` 加一。
|
||
|
||
## 追加模板
|
||
|
||
```markdown
|
||
### N. <一句话>
|
||
- 现象:
|
||
- 为什么会错:
|
||
- 以后怎么防:
|
||
- 首次:YYYY-MM-DD|复发:0
|
||
```
|
||
|
||
## 索引
|
||
|
||
| # | 一句话 | 首次 |
|
||
|---|---|---|
|
||
| 1 | 类型判断只写 `int`,漏掉 `np.int64` 这类 numpy 标量 | 2026-10-01 |
|
||
| 2 | 以为 `# TODO(L0)` 只是普通注释 | 2026-10-01 |
|
||
| 3 | 签名里的类型注解和 docstring 说的不一致 | 2026-10-01 |
|
||
| 4 | 只覆盖自己的状态,忘了第三方库还在用 numpy 旧全局 | 2026-10-01 |
|
||
|
||
---
|
||
|
||
### 1. 类型判断只写 `int`,漏掉 `np.int64` 这类 numpy 标量
|
||
|
||
- 现象:`get_rng(np.int64(42))` 不报错,但种子被静默忽略、退回全局流——结果不可复现,且没有任何提示。
|
||
- 为什么会错:`np.int64` **不是** python `int` 的子类(`isinstance(np.int64(42), int)` 是 `False`),
|
||
但 `np.float64` **是** `float` 的子类。numpy 标量的继承关系不统一,凭直觉一定会翻车。
|
||
- 以后怎么防:写类型分支时默认假设"这个值可能来自 numpy"。判断用 `isinstance(x, (int, np.integer))`,
|
||
取值统一 `int(x)` 归一化;并且给所有非预期类型加 `raise TypeError` 兜底——
|
||
**宁可报错,也不要静默走默认分支**。
|
||
- 首次:2026-10-01|复发:0
|
||
|
||
### 2. 以为 `# TODO(L0)` 只是普通注释
|
||
|
||
- 现象:函数实现完了,`# TODO(L0)` 还挂在末尾。
|
||
- 为什么会错:本仓库把 `TODO(L0)` / `TODO(L1)` / `TODO(v0.2)` 当**进度标记**用:
|
||
`docs/03-roadmap.md` 靠它划分档位,`STATUS.md` 靠它统计"还剩哪些没做"。实现完不删,等于骗自己。
|
||
- 以后怎么防:实现完成时顺手删掉该函数里的 `TODO(...)`。
|
||
全仓还剩多少没做,一条 `rg "TODO\(" src/` 就能数出来。
|
||
- 首次:2026-10-01|复发:0
|
||
|
||
### 3. 签名里的类型注解和 docstring 说的不一致
|
||
|
||
- 现象:`def get_rng(random_state: int | None = None)`,docstring 却写"已经是 `np.random.Generator` 时原样返回"。
|
||
- 为什么会错:类型注解是给**读代码的人和 pyright** 看的契约。注解说只收 int/None、docstring 说还收 Generator,
|
||
两处打架时合法用法会被 IDE 标红,真正的错误反而看不出来。
|
||
- 以后怎么防:改签名和改 docstring 必须同一次做完。
|
||
本项目所有 `random_state` 的注解统一写 `int | np.integer | np.random.Generator | None`。
|
||
- 首次:2026-10-01|复发:1
|
||
|
||
### 4. 只覆盖自己的状态,忘了第三方库还在用 numpy 旧全局
|
||
|
||
- 现象:`set_seed` 换掉了本包的生成器,但 sklearn / statsmodels 里 `random_state=None` 的路径
|
||
仍不可复现(测试直接挂)。
|
||
- 为什么会错:numpy 有**两套**随机状态——旧的 `np.random.*` 全局单例,和新的 `Generator`。
|
||
第三方库大量仍在走旧的那套(`check_random_state(None)`),自己新建一个 Generator 管不到它们。
|
||
- 以后怎么防:凡是"设全局状态"的函数,先列清单——自己的、numpy 旧的、标准库 `random` 的、
|
||
第三方框架的,逐个决定要不要一起设,并在 docstring 里写清到底覆盖了哪几个。
|
||
- 首次:2026-10-01|复发:0
|