Files
MCM-ICM_Toolkit/docs/dev/personal-pitfalls.md
T
guzhujushi c93810ffb1 fix(core): 补齐 get_rng 类型校验,set_seed 覆盖 numpy 旧全局
get_rng 的"共享单例生成器"设计由用户本人提出,本提交只在其上修 bug。

- random_state 判断从 isinstance(x, int) 放宽到 (int, np.integer):
  np.int64 不是 int 的子类,之前会静默退回全局流、种子被无声忽略
- 非预期类型改成抛 TypeError(含 bool / float / str / list),不再静默降级
- get_rng 签名补上 np.integer 与 Generator,与 docstring 的承诺一致
- set_seed 同时调用 np.random.seed:sklearn / statsmodels 走的是 numpy
  旧全局状态,不覆盖它们"提交能重跑"就有洞(带 noqa: NPY002 与理由)
- 测试:TestRandom 由 4 条增到 7 条(np.integer、非法类型、共享生成器推进)
- 新增 docs/dev/personal-pitfalls.md:个人易错点清单,只追加、不通读,
  首批记录本轮 4 个点;docs/README.md 补上 05 与该文件的索引
- STATUS.md:L0 进度说明更新,决策 24 改写(共享单例)、新增决策 25
  (src/ 实现默认由用户本人写)
2026-10-01 19:38:59 +08:00

3.8 KiB
Raw Blame History

个人易错点(只追加)

本文件的规矩

  1. 只追加。 已有条目一个字都不改、不重排、不润色。发现写错了 → 在末尾补一条"更正",不要回头改。
  2. 不通读。 追加前只做两件事:rg -i "<关键词>" docs/dev/personal-pitfalls.md 看有没有记过; 看一眼下面的索引表最后一行拿编号。全文只在复盘时才读。
  3. 一条写三行:现象 / 为什么会错 / 以后怎么防。 写不出"为什么会错"就先别记——那说明还没想清楚。
  4. 记规律,不记事故。"今天又忘了"不新开条目,在已有条目下把 复发 加一。

追加模板

### 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|复发:0

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