Python 脚本可复现的三个习惯:随机种子、相对路径与依赖锁定
同一份脚本,今天跑出一个结果,明天换台机器又跑出另一个结果,这种问题往往不是算法错了,而是可复现性被三个细节悄悄破坏了:随机性没有固定、路径写死了、依赖没有锁定。本文把这三个习惯讲清楚,并给出可以直接落地的写法。
一、固定随机种子,但要固定全套
只设 random.seed 是不够的。NumPy、PyTorch 等库各自维护随机状态,数据加载器的洗牌也可能引入随机性。正确做法是把入口处集中设置,并在脚本开头打印种子,方便事后复查。
import random
import numpy as np
SEED = 42
def set_seed(seed: int) -> None:
random.seed(seed)
np.random.seed(seed)
# 如使用 PyTorch,还需设置 torch.manual_seed(seed)
# 并注意数据加载器的 shuffle 与 worker 随机状态
set_seed(SEED)
print(f"seed={SEED}")
需要注意两点:第一,种子只能保证同一环境、同一版本下的确定性,不能保证跨大版本一定相同;第二,并行与 GPU 归约顺序可能带来微小差异,关键结论不要只依赖最后一位小数。
二、用相对路径锚定项目根目录,而不是绝对路径
把 /Users/xxx/project/data.csv 写进脚本,换一台机器立刻失效;把路径写成相对路径又容易受启动目录影响。更稳妥的做法是以当前文件为锚点推导项目根目录,再拼接数据路径。
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
DATA_PATH = ROOT / "data" / "input.csv"
OUTPUT_PATH = ROOT / "output" / "result.csv"
OUTPUT_PATH.parent.mkdir(parents=True, exist_ok=True)
这样做的好处是:脚本无论从哪个目录启动,指向的都是同一份数据;输出目录不存在时自动创建;把项目整体拷贝到另一台机器也能直接运行。输出文件建议带上日期或种子后缀,避免新结果静默覆盖旧结果。
三、锁定依赖:requirements 里不要只写包名
只写 pandas 这样的包名,等于把版本选择权交给了安装当天的最新版。几个月后重装环境,API 行为可能已经变了。最小可行的锁定方式有两种:
- 开发时用 pip freeze 生成完整依赖清单,提交到版本库;
- 在 requirements 里至少给核心依赖写上下限,例如 pandas>=2.0,<3.0。
pip freeze > requirements.txt
# 部署或复现时使用同一份清单安装
pip install -r requirements.txt
如果项目同时涉及系统库或模型权重,也要把它们的版本、来源记录在 README 里。只锁定 Python 包,却忽略模型文件版本,同样不算真正可复现。
四、上线前自查清单
- 重新拉取一份干净代码,在新目录跑通一次;
- 删除输出目录后重跑,确认结果与上次一致;
- 检查脚本里是否还残留绝对路径、硬编码的账号口令或临时文件名;
- 把种子、关键参数和依赖清单写进运行日志或结果文件头部。
小结
可复现不是玄学,而是三件小事的组合:入口固定全套随机种子、以文件位置锚定路径、用清单锁定依赖。把这三个习惯写进每个脚本的模板里,排障时先排除环境差异,再去怀疑算法本身,往往能省下大量时间。
- 点赞
- 收藏
- 关注作者
评论(0)