docs: L0 进度 3/12,记录 zstandard 残骸坑与决策 26/27

- 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 段
This commit is contained in:
guzhujushi committed 2026-10-01 23:11:48 +08:00
1 parent b6456d97d8
commit 49fd84a2df
4 files changed
+82 -13

No files matched your search

+15 -2
View File
@@ -26,16 +26,29 @@
`set_seed` 同时覆盖 numpy 旧全局状态,让 sklearn / statsmodels 也可复现。 `set_seed` 同时覆盖 numpy 旧全局状态,让 sklearn / statsmodels 也可复现。
- `core.Result`:补上 `__repr__` 与 `summary()` 实现(L0 第 1 项落地)。 - `core.Result`:补上 `__repr__` 与 `summary()` 实现(L0 第 1 项落地)。
`repr` 紧凑单行且不打印 `data` 内容;`summary()` 输出可直接贴进论文的 Markdown 表格。 `repr` 紧凑单行且不打印 `data` 内容;`summary()` 输出可直接贴进论文的 Markdown 表格。
- `data.load_table`:实现(L0 第 3 项)。按扩展名分派 `.csv` / `.xlsx` / `.xls`;
`.csv` 默认做编码探测(utf-8 → utf-8-sig → gbk → gb18030),显式传 `encoding=` 时不再探测;
`na_values` 默认吃下空串 / `NA` / `N/A` / `#N/A` / `null` / `-`。
- `data.save_table`:实现(原属 L1,顺手落地)。按扩展名分派,父目录不存在时自动创建,
返回实际写入的 `Path`。
### Added ### Added
- `docs/02-architecture.md` §4.7「类型约定」:注解要写全、numpy 标量的判断写法、 - `docs/02-architecture.md` §4.7「类型约定」:注解要写全、numpy 标量的判断写法、
非法类型必须抛 `TypeError`。配套在 `docs/03-roadmap.md` 的开发纪律里加了指针。 非法类型必须抛 `TypeError`。配套在 `docs/03-roadmap.md` 的开发纪律里加了指针。
- `docs/dev/personal-pitfalls.md`:个人易错点清单,**只追加、不通读** - `docs/dev/personal-pitfalls.md`:个人易错点清单,**只追加、不通读**
### Fixed
- `tests/test_data.py` 顶上的**模块级** `pytestmark = pytest.mark.skip(...)` 会连带跳过同文件里
已经写好的函数,导致 `load_table` 零覆盖。改为逐条 skip 未实现的算法,并补 8 条 io 测试
(编码探测、NA 归一、后缀校验、缺文件报错、往返读写、类型校验)。测试数 31 → **39 passed**。
- conda base 里 zstandard 的 `*.conda_trash` 残骸目录被 Python 当成 namespace package 导入,
使 `.venv`(= base)的 `pd.read_csv` 抛 `ImportError: Can't determine version for zstandard`。
已把残骸目录改名禁用(**可逆**,未删除),详见 `docs/04-environment.md` 坑 6。
### Notes ### Notes
- 除 `core.Result`、`core.get_rng`、`core.set_seed` 外,其余函数体**尚未实现**(统一 `raise NotImplementedError` + `TODO(L0/L1/v0.2)` 标记)。 - 除 `core.Result`、`core.get_rng`、`core.set_seed`、`data.load_table`、`data.save_table` 外,其余函数体**尚未实现**(统一 `raise NotImplementedError` + `TODO(L0/L1/v0.2)` 标记)。
这是刻意的:先定接口,再按 `docs/03-roadmap.md` 的 L0 → L1 顺序填实现。 这是刻意的:先定接口,再按 `docs/03-roadmap.md` 的 L0 → L1 顺序填实现。
- 已知缺口:`SALib`(Sobol/Morris)、`pymoo`、`pyright` 未安装(本机离线)。 - 已知缺口:`SALib`(Sobol/Morris)、`pymoo`、`pyright` 已装进 conda 环境 `mcm-toolkit`,`.venv` 里没有。
## [0.0.1] - 2026-10-01 ## [0.0.1] - 2026-10-01
+31 -10
View File
@@ -4,9 +4,13 @@
## 本次任务 ## 本次任务
审查并精简 ChatGPT 的建库建议 → 生成骨架文件 → 配置 conda 与 venv 环境 → 初始化 git 仓库 → 接入 Gitea 远端。 **第一阶段(已完成)**:审查并精简 ChatGPT 的建库建议 → 生成骨架文件 →
配置 conda 与 venv 环境 → 初始化 git 仓库 → 接入 Gitea 远端。
两套环境均已建成并通过验证,仓库已推送到 `code.guzhujushi.cn`。
**结论:全部完成。** 两套环境均已建成并通过验证;仓库已推送到 `code.guzhujushi.cn`。 **第二阶段(进行中)**:按 `docs/03-roadmap.md` 的 L0 → L1 顺序**逐个实现函数**,
用户本人写 `src/`、Codex 负责审查 + `tests/`(决策 25)。
当前 3/12:`Result`、`get_rng`/`set_seed`、`load_table`/`save_table`。
## 任务清单 ## 任务清单
@@ -58,7 +62,7 @@
- [x] 推送通道:公网 22 被防火墙过滤 → 改用 Tailscale(`100.115.86.48:22`), - [x] 推送通道:公网 22 被防火墙过滤 → 改用 Tailscale(`100.115.86.48:22`),
`~/.ssh/config` 加了一条域名别名,remote 里仍写域名 `~/.ssh/config` 加了一条域名别名,remote 里仍写域名
**E. L0 实现进度(2/12)** **E. L0 实现进度(3/12)**
- [x] `core/result.py` 的 `Result` - [x] `core/result.py` 的 `Result`
- `__repr__`:单行紧凑;`data` 只给类型与规模,**绝不**整数组打印 - `__repr__`:单行紧凑;`data` 只给类型与规模,**绝不**整数组打印
(十万级数组的 repr 实测仍 < 200 字符);`success` 恒在 (十万级数组的 repr 实测仍 < 200 字符);`success` 恒在
@@ -75,7 +79,14 @@
- `set_seed` 同时调 `np.random.seed`,覆盖 sklearn / statsmodels 走的 numpy 旧全局 - `set_seed` 同时调 `np.random.seed`,覆盖 sklearn / statsmodels 走的 numpy 旧全局
(全项目**唯一**碰旧接口的地方,带 `# noqa: NPY002` 与理由) (全项目**唯一**碰旧接口的地方,带 `# noqa: NPY002` 与理由)
- 测试:`TestRandom` 共 7 条(可复现、透传、无参跟随、旧全局、np.integer、非法类型、共享推进) - 测试:`TestRandom` 共 7 条(可复现、透传、无参跟随、旧全局、np.integer、非法类型、共享推进)
- [ ] 剩余 10 项(11 个函数):`load_table` `clean_missing` - [x] `data/io.py` 的 `load_table`(+ `save_table`,原属 L1,顺手落地)
- 实现由**用户本人**写(决策 25),Codex 只做审查 + 补测试
- `.csv` / `.xlsx` / `.xls` 按扩展名分派;`.csv` 默认编码探测
utf-8 → utf-8-sig → gbk → gb18030,显式传 `encoding=` 时不再探测
- `na_values` 默认吃下空串 / `NA` / `N/A` / `#N/A` / `null` / `-`
- 不支持的扩展名抛 `ValueError`;文件不存在抛带路径的 `FileNotFoundError`
- 测试:`tests/test_data.py` 由 1 条(且被模块级 skip 掉)扩到 **8 条**
- [ ] 剩余 9 项(10 个函数):`clean_missing`
`detect_outliers` `normalize` `standardize` `describe` `correlation_matrix` `detect_outliers` `normalize` `standardize` `describe` `correlation_matrix`
`bootstrap_ci` `monte_carlo` `save_figure` `bootstrap_ci` `monte_carlo` `save_figure`
@@ -83,15 +94,16 @@
| 检查 | venv | conda `mcm-toolkit` | | 检查 | venv | conda `mcm-toolkit` |
|---|---|---| |---|---|---|
| `pytest` | 31 passed, 45 skipped | 31 passed, 45 skipped | | `pytest` | **39 passed, 44 skipped** | **39 passed, 44 skipped** |
| `import mcm_toolkit` | 0.0.1 / 16 项 | 0.0.1 / 16 项 | | `import mcm_toolkit` | 0.0.1 / 16 项 | 0.0.1 / 16 项 |
| `ruff check` | All checks passed | All checks passed | | `ruff check` | All checks passed | All checks passed |
| `scripts/doctor.ps1` | 跑通 | — | | `scripts/doctor.ps1` | 跑通 | — |
| SALib / pymoo / pyright | 缺失 | 已装 | | SALib / pymoo / pyright | 缺失 | 已装 |
- base 亦已复核:numpy 2.4.6 / scipy 1.18.1 / pandas 3.0.6(与两套环境一致) - base 亦已复核:numpy 2.4.6 / scipy 1.18.1 / pandas 3.0.6(与两套环境一致)
- skip 的都是等实现的算法行为测试;通过的 31 条 = 20 条公开 API 契约测试 - skip 的都是等实现的算法行为测试;通过的 39 条 = 20 条公开 API 契约测试
(`tests/test_api_surface.py`)+ 11 条 `core` 行为测试(`tests/test_core.py`) (`tests/test_api_surface.py`)+ 11 条 `core` 行为测试(`tests/test_core.py`)
+ 8 条 `data` 行为测试(`tests/test_data.py`)
- 三个 `.ps1` 通过 PowerShell AST 语法解析 - 三个 `.ps1` 通过 PowerShell AST 语法解析
### ⚠️ 需要注意 / 遗留 ### ⚠️ 需要注意 / 遗留
@@ -101,7 +113,7 @@
只差 Python 补丁号(3.13.9 vs 3.13.15)。详见 `docs/04-environment.md` §5。 只差 Python 补丁号(3.13.9 vs 3.13.15)。详见 `docs/04-environment.md` §5。
- [x] ~~Gitea 远程尚未配置~~ → **已配置并推送完成**,见上面 D 节与决策 19/20。 - [x] ~~Gitea 远程尚未配置~~ → **已配置并推送完成**,见上面 D 节与决策 19/20。
note:仓库当前是 **Public**(匿名 `git ls-remote` 可读),要改私有去 Gitea 仓库设置。 note:仓库当前是 **Public**(匿名 `git ls-remote` 可读),要改私有去 Gitea 仓库设置。
- [ ] L0 完成 2/12(`Result`、`get_rng`/`set_seed`),其余按 `docs/03-roadmap.md` 的 L0 → L1 顺序推进; - [ ] L0 完成 **3/12**(`Result`、`get_rng`/`set_seed`、`load_table`/`save_table`),其余按 `docs/03-roadmap.md` 的 L0 → L1 顺序推进;
详情见上面 E 节 详情见上面 E 节
- [ ] 本机 `exec_command` 在沙箱模式下**完全不可用** - [ ] 本机 `exec_command` 在沙箱模式下**完全不可用**
(`helper_unknown_error: setup refresh had errors`),只有提权 (`helper_unknown_error: setup refresh had errors`),只有提权
@@ -142,11 +154,20 @@
| 23 | `set_seed` 内部**允许**调用 `np.random.seed`(带 noqa) | sklearn / statsmodels 默认走 numpy 旧全局状态,只记自己的 `_GLOBAL_SEED` 覆盖不到它们,"提交能重跑"就是假的。旧接口在这一个点上不是"取随机数"而是"设种子",性质不同 | | 23 | `set_seed` 内部**允许**调用 `np.random.seed`(带 noqa) | sklearn / statsmodels 默认走 numpy 旧全局状态,只记自己的 `_GLOBAL_SEED` 覆盖不到它们,"提交能重跑"就是假的。旧接口在这一个点上不是"取随机数"而是"设种子",性质不同 |
| 24 | `get_rng(None)` **复用同一个模块级生成器**(会推进),不每次新建 | **用户本人提出**,取代 Codex 原来"每次新建 `default_rng(全局种子)`"的写法——那样两个都省略 `random_state` 的函数会抽到完全相同的数。共享单例既保证重跑一致,又让流自然推进 | | 24 | `get_rng(None)` **复用同一个模块级生成器**(会推进),不每次新建 | **用户本人提出**,取代 Codex 原来"每次新建 `default_rng(全局种子)`"的写法——那样两个都省略 `random_state` 的函数会抽到完全相同的数。共享单例既保证重跑一致,又让流自然推进 |
| 25 | `src/` 下的实现默认由**用户本人**写,Codex 只改 `tests/` 与 `docs/` | 目标是"一边学一边自己写"。曾出现 Codex 直接落盘 `random.py`、与用户编辑器缓冲区冲突导致保存失败。需要代写时用户会明说 | | 25 | `src/` 下的实现默认由**用户本人**写,Codex 只改 `tests/` 与 `docs/` | 目标是"一边学一边自己写"。曾出现 Codex 直接落盘 `random.py`、与用户编辑器缓冲区冲突导致保存失败。需要代写时用户会明说 |
| 26 | 未实现的算法用**逐条** `@pytest.mark.skip`,禁用模块级 `pytestmark` | 模块级 skip 会把同文件里**已经写好**的函数一起跳过。本次 `test_data.py` 就这样让 `load_table` 零覆盖,直到有人手动数才被发现 |
| 27 | base 里的 zstandard 残骸目录**改名禁用**,不删除、不重装 | 删掉不可逆;`pip install zstandard` 会给 base 塞一个 conda 记录里根本没有的包,下次 `conda update --all` 又要重新判断。改名后 `import` 变回正常 `ModuleNotFoundError`,pandas 自动跳过 |
## 下一步 ## 下一步
1. ~~接入 Gitea~~ **已完成**。以后推送前先确认 Tailscale 在线,然后 `git push` 1. **`data/io.py` 待你修两处**(`src/` 归你,Codex 不代写,见决策 25):
(细节与换机步骤见 `docs/05-git-workflow.md` §3)。 - `save_table` 把 `path.parent.mkdir(parents=True, exist_ok=True)` 放在了**后缀校验之前**,
2. 继续 L0,**下一个:`data/io.py` 的 `load_table`**(`save_table` 属 L1,顺手也可做)。 传个 `.txt` 会先建出一个空目录再抛 `ValueError`。
把校验(后缀 + `isinstance(df, pd.DataFrame)`)挪到 `mkdir` 前面即可。
- 两个函数都缺 `Raises` 段落(`docs/02-architecture.md` §4.7 要求写全);
`load_table` 的返回注解建议从 `Any` 改成 `pd.DataFrame` —— docstring 已经承诺返回 DataFrame,
注解写 `Any` 等于同一个契约写了两遍(`docs/dev/personal-pitfalls.md` 条目 3,本次已复发一次)。
2. 继续 L0,**下一个:`data/clean.py` 的 `clean_missing`**(L0 / A 档,直接能写)。
同文件的 `detect_outliers` 是 **S 档**,要先按 `docs/dev/function-card-template.md`
把函数卡片写出来再动手。
3. 每写完一个函数,在 `examples/` 里加一行最小用法,并更新本文件。 3. 每写完一个函数,在 `examples/` 里加一行最小用法,并更新本文件。
4. L0 跑通后,找一道真题(建议 2025 C)走完整流程,反向沉淀函数。 4. L0 跑通后,找一道真题(建议 2025 C)走完整流程,反向沉淀函数。
+35
View File
@@ -129,6 +129,41 @@ Remove-Item -LiteralPath 'D:\anaconda\envs\mcm-toolkit' -Recurse -Force
环境拿到的是更新的版本(pandas 从 2.3.3 跳到 **3.0.6**,是 major 升级)。 环境拿到的是更新的版本(pandas 从 2.3.3 跳到 **3.0.6**,是 major 升级)。
见 §5,写代码时注意别依赖两套环境都有的行为。 见 §5,写代码时注意别依赖两套环境都有的行为。
**坑 6 —— conda 升级留下的 `*.conda_trash` 残骸会被 Python 当成包导入。**
base 从 2025.12 升到 2026.07 后,`D:\anaconda\Lib\site-packages\zstandard\` 里只剩一个
`backend_c.cp313-win_amd64.pyd.c~.conda_trash`。目录没了 `__init__.py`,Python 就把它当作
**namespace package** 导入成功,于是:
```text
>>> import zstandard
>>> zstandard.__file__ # None —— 导入"成功"了,但什么都没拿到
>>> zstandard.__version__
AttributeError
```
pandas 3.0 的 `import_optional_dependency("zstandard")` 拿到这种半残包时,抛的不是
`ModuleNotFoundError`(那会被正常忽略),而是
`ImportError: Can't determine version for zstandard`,直接**打断 `pd.read_csv`**。
症状很迷惑:同一个脚本今天能跑明天不能跑,报错还指向 csv 读取。
**怎么查**(关键是 `__file__` 和 `__version__` 两项一起看):
```powershell
& 'D:\anaconda\python.exe' -c "import zstandard; print(zstandard.__file__, getattr(zstandard,'__version__','MISSING'))"
Get-ChildItem -LiteralPath 'D:\anaconda\Lib\site-packages\zstandard' -Force
```
**怎么修**:确认 conda 记录里本来就没有这个包(`conda list -n base | findstr zstandard`
只该看到 `backports.zstd` / `zstd`),再把这个残骸目录改名禁用(比删掉可逆):
```powershell
Move-Item -LiteralPath 'D:\anaconda\Lib\site-packages\zstandard' `
-Destination 'D:\anaconda\Lib\site-packages\zstandard.disabled-conda-trash'
```
改名后 `import zstandard` 变成正常的 `ModuleNotFoundError`,pandas 会安静地跳过它。
本次已按此处理,`.venv` 的 `pd.read_csv` 恢复正常。
--- ---
## 五、两套环境的实际版本(已对齐) ## 五、两套环境的实际版本(已对齐)
+1 -1
View File
@@ -55,7 +55,7 @@
两处打架时合法用法会被 IDE 标红,真正的错误反而看不出来。 两处打架时合法用法会被 IDE 标红,真正的错误反而看不出来。
- 以后怎么防:改签名和改 docstring 必须同一次做完。 - 以后怎么防:改签名和改 docstring 必须同一次做完。
本项目所有 `random_state` 的注解统一写 `int | np.integer | np.random.Generator | None`。 本项目所有 `random_state` 的注解统一写 `int | np.integer | np.random.Generator | None`。
- 首次:2026-10-01|复发:0 - 首次:2026-10-01|复发:1
### 4. 只覆盖自己的状态,忘了第三方库还在用 numpy 旧全局 ### 4. 只覆盖自己的状态,忘了第三方库还在用 numpy 旧全局