Files
MCM-ICM_Toolkit/docs/04-environment.md
T
guzhujushi 49fd84a2df 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 段
2026-10-01 23:11:48 +08:00

274 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 环境配置
> 记录本机(Windows / PowerShell)上 conda 与 venv 两套环境的**实际**配置方式、
> 实测数据、以及踩过的坑。相关脚本在 `scripts/`。
>
> 状态:**两套环境均已建好、版本已对齐、验证通过**(2026-10-01)。
---
## 一、结论速查
| | venv | conda env |
|---|---|---|
| 路径 | `D:\MCM&ICM_Toolkit\.venv` | `D:\anaconda\envs\mcm-toolkit` |
| 解释器 | Anaconda base,Python **3.13.9** | Python **3.13.15** |
| 占用 | 10.5 MB | 1.55 GB(+ 共用 pkgs 缓存) |
| 来源 | `--system-site-packages` 复用 base | conda 从 defaults 通道安装 |
| 用途 | 轻量开发 / 离线兜底 | **跑题主力环境** |
| 缺什么 | SALib、pymoo、pyright | 无缺口 |
**两套的数值计算栈已完全一致**(见 §5),只差 Python 补丁号。可以放心在两套之间切换。
---
## 二、venv(轻量、离线可用)
```powershell
D:\anaconda\python.exe -m venv --system-site-packages --prompt "mcm-toolkit" .venv
.venv\Scripts\python.exe -m pip install -e . --no-deps --no-build-isolation
```
一键:`./scripts/setup-venv.ps1`
### 为什么用 `--system-site-packages`
让 venv 直接复用 Anaconda base 里已有的包,**零下载**就有可用的科学栈,
而且只有 10.5 MB,可以安心放在项目目录里。
已验证可导入(版本随 base 走,2026-10-01 base 升级后为):
`text
numpy 2.4.6 scipy 1.18.1 pandas 3.0.6 matplotlib 3.11.2
scikit-learn 1.9.1 statsmodels 0.15.0 networkx 3.6.1 sympy 1.14.0
`
### `--no-deps --no-build-isolation` 是必须的
* `--no-deps`:依赖已由 base 提供,不必联网解析;
* `--no-build-isolation`:否则 pip 会新建隔离环境去下载 setuptools 构建后端。
---
## 三、conda 环境 `mcm-toolkit`(跑题主力)
```powershell
# 一键(推荐)
.\scripts\setup-conda.ps1
# 等价的手工命令
$env:CONDA_PLUGINS_AUTO_ACCEPT_TOS = 'yes' # 见 §4 坑 1
conda create -n mcm-toolkit -y python=3.13 ^
numpy scipy pandas matplotlib scikit-learn statsmodels ^
networkx sympy openpyxl pytest ruff pip
```
**实测:约 14.5 分钟(868 秒),下载约 3 GB,环境本体 1.55 GB。**
建完后补装 pip-only 的依赖并安装本项目:
```powershell
$py = 'D:\anaconda\envs\mcm-toolkit\python.exe'
& $py -m pip install SALib pymoo pyright
& $py -m pip install -e . --no-deps --no-build-isolation
```
这些 `setup-conda.ps1` 已全部自动完成。
### 为什么不推荐 `--clone base`
原以为 `conda create -n X --clone base` 是"本地复制、秒级完成、不用下载"。
**实测不是。** 在 conda 25.11 上它会重放 base 的完整包列表并逐个下载安装
(连 pandoc、m2-msys2-runtime、anaconda-navigator 这些都用不上的也照下),
跑 253 秒才下完约 2 GB 就已被中止,按比例算远慢于精准列表。
所以 `setup-conda.ps1` 的默认路径是**精准包列表**;`-CloneBase` 只在你确实想
要一份 base 的完整副本时才用。
### 断网怎么办
**提前建好环境**,并保留 `D:\anaconda\pkgs` 缓存(现在 8.4 GB)。
包已在缓存里时 `conda create --offline` 才能工作;否则会失败。
比赛现场不要指望临时建环境,走 venv(它 100% 离线可用)。
---
## 四、踩过的坑
**坑 1 —— Anaconda ToS 插件要联网。**
Anaconda 25.x 自带 `conda_anaconda_tos` 插件,任何 `create/install` 之前都会去
`repo.anaconda.com` 拉 ToS 元数据。断网时它不是报"没网",而是抛一长串 traceback。
```powershell
$env:CONDA_PLUGINS_AUTO_ACCEPT_TOS = 'yes' # 推荐:跳过联网,保留 libmamba 求解器
conda --no-plugins create ... # 彻底关插件(退化成经典求解器,慢)
```
**坑 2 —— `envs_dirs` / `pkgs_dirs` 必须可写。**
不可写时报 `NotWritableError` 或
`Error encountered while attempting to create cache directory`。排查:
```powershell
conda info --json | Select-String "envs_dirs","pkgs_dirs"
conda config --show envs_dirs pkgs_dirs
```
**坑 3 —— 被中止的 `conda create` 用 `conda env remove` 删不干净。**
本次中止后 `conda env remove -n mcm-toolkit -y` 跑完,`D:\anaconda\envs\mcm-toolkit`
**仍然存在**且 `conda env list` 里已看不到它。要手工删目录:
```powershell
Remove-Item -LiteralPath 'D:\anaconda\envs\mcm-toolkit' -Recurse -Force
```
**坑 4 —— 别把 conda env 建在项目目录里。**
`conda create -p ./.conda` 会把 GB 级目录放进仓库,脏 `.gitignore` 也容易误提交。
**坑 5 —— 版本漂移。**
本机 base 是 2025-10 装的,defaults 通道已是 2026-10 的版本,于是新建的 conda
环境拿到的是更新的版本(pandas 从 2.3.3 跳到 **3.0.6**,是 major 升级)。
见 §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` 恢复正常。
---
## 五、两套环境的实际版本(已对齐)
**2026-10-01 已把 base 从 Anaconda 2025.12 升到 2026.07,两套环境版本已对齐。**
| 包 | venv(继承 base) | conda `mcm-toolkit` | |
|---|---|---|---|
| Python | 3.13.9 | 3.13.15 | 补丁号,可忽略 |
| numpy | 2.4.6 | 2.4.6 | ✅ 一致 |
| scipy | 1.18.1 | 1.18.1 | ✅ 一致(连 build 号都相同) |
| pandas | 3.0.6 | 3.0.6 | ✅ 一致 |
| matplotlib | 3.11.2 | 3.11.2 | ✅ 一致 |
| scikit-learn | 1.9.1 | 1.9.1 | ✅ 一致 |
| statsmodels | 0.15.0 | 0.15.0 | ✅ 一致 |
| networkx | 3.6.1 | 3.6.1 | ✅ 一致 |
| sympy | 1.14.0 | 1.14.0 | ✅ 一致 |
| pytest | 9.1.1 | 9.1.1 | ✅ 一致 |
| SALib / pymoo / pyright | 缺失 | 已装 | venv 本来就只做轻量开发 |
| BLAS 后端 | **MKL** | **MKL** | ✅ 一致 |
### base 升级记录
[FENCE]powershell
conda update -n base --all -y # 365 个包更新,下载 638 MB,耗时 1033 秒
conda install -n mcm-toolkit numpy=2.4.6 -y # 把项目环境对齐到 base 的 numpy
[FENCE]
* `conda` 本身从 25.11.1 升到 **26.7.3**。
* 升级前 `conda list -n base --revisions` 里已留有 revision 记录,可回滚:
`conda install -n base --revision <N>`。
* **踩过的坑**:base 想装 `numpy=2.5.3` 时求解器会顺手把 BLAS 从 **MKL 换成 OpenBLAS**
(`_anaconda_depends 2026.07 ..._mkl_1 → ..._openblas_1`、`blas 1.0-mkl → 1.0-openblas`)。
`_anaconda_depends` 把 base 的 numpy 钉在 2.4.6,硬升会牵动整套线性代数后端,
所以选择把项目环境的 numpy **降到 2.4.6** 来对齐,而不是动 base。
* 升级期间遇到 `repo.anaconda.com` 读超时(默认 9.15 秒太短),已把
`remote_read_timeout_secs` 调到 120、`remote_connect_timeout_secs` 调到 30、
`remote_max_retries` 调到 5。这几项写在 `~/.condarc` 里。
## 六、国内镜像(需要装新包时)
```powershell
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main
conda config --set show_channel_urls yes
# pip(对 venv 和 conda 环境都生效)
python -m pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
```
> 本机当前通过 `http://127.0.0.1:7993` 的 UniClash 代理直连,未配镜像。镜像只影响**下载**,
> 不影响坑 1 的 ToS 行为。
---
## 七、可选依赖
核心 4 个包(`numpy scipy pandas matplotlib`)两套环境都有。其余按需:
```powershell
python -m pip install -e ".[ml]" # sklearn statsmodels 已有
python -m pip install -e ".[graph]" # networkx 已有
python -m pip install -e ".[sensitivity]" # SALib conda 环境已有
python -m pip install -e ".[symbolic]" # sympy 已有
python -m pip install -e ".[moo]" # pymoo conda 环境已有
python -m pip install -e ".[excel]" # openpyxl 已有
python -m pip install -e ".[dev]" # pytest ruff pyright
```
缺依赖时对应函数应当给出可读的安装提示,而不是裸抛 `ImportError`
(写成约定,见 `docs/02-architecture.md` §4.6)。
---
## 八、脚本的编码约定(踩过的坑)
`scripts/` 下的 `.ps1` **必须是纯 ASCII + UTF-8 无 BOM + LF**。
原因是 Windows PowerShell 5.1 **在没有 BOM 时按 ANSI(本机 GBK)解析 `.ps1`**,
中文注释和中文消息会被拆成非法字节,报出来的却是一堆莫名其妙的
`Missing closing '}' in statement block` / `The string is missing the terminator`。
(实测:带中文的 `doctor.ps1` 直接语法错误;改成纯 ASCII 后立刻正常。)
两种可行方案,本项目选了第二种:
1. 保留中文 → 必须存成 **UTF-8 with BOM**(违反 AGENTS.md 的"无 BOM"约定);
2. **纯 ASCII,注释与输出用英文** → 符合 AGENTS.md,且任何编码下都能解析。← 采用
另一个相关的坑:**PowerShell 5.1 向原生程序传参时会吃掉双引号**,
所以 `doctor.ps1` 里传给 `python -c` 的探测代码只能用单引号字符串。
---
## 九、已知的沙箱相关现象
如果在受限环境(文件系统只读 / 无网络)里跑 matplotlib,会看到:
```text
Matplotlib created a temporary cache directory at ... because there was an issue with
the default path (C:\Users\guzhu\.matplotlib)
```
这不是配置问题,是 `~\.matplotlib` 不可写。正常终端里不会出现;
真遇到时设置 `MPLCONFIGDIR` 指向可写目录即可:
```powershell
$env:MPLCONFIGDIR = "$env:TEMP\matplotlib"
```