4. 环境导出与导入(environment.yml)
环境导出与导入,说白了就是给你的 Conda 环境拍一张「快照」,然后在别的地方能原样还原出来。我在做可重复性验证时,最常被问到的问题就是——「怎么保证你跑出来的结果,我能原样跑出来?」答案就在 environment.yml 这个文件里。
你可能会问,直接把整个环境文件夹打包带走不行吗?当然可以。但那样体积大、跨平台容易出问题,也没法用 Git 做版本管理。environment.yml 是一个纯文本的 YAML 文件,记录了环境的所有依赖信息。轻量、可读、可版本控制——这才是可重复性的正确打开方式。
4.1 为什么需要 environment.yml
我见过不少团队,项目跑了一年多,换台机器就装不回原来的环境了。原因很简单:没人记得当初装了哪些包、什么版本。脑子里记的「大概装了 numpy、pandas、scikit-learn」——这种信息根本没法复现。
environment.yml 解决了三个核心问题:
- 精确记录——每个依赖包的名字和版本号都被刻在文件里
- 跨机器复现——换一台 Linux、macOS 或 Windows 机器,都能重建出一模一样的环境
- 版本控制友好——纯文本文件,可以直接塞进 Git,每次修改都有记录
我个人习惯,每个项目的第一件事就是初始化一个 environment.yml,跟在项目根目录下。这样不管谁拉取代码,第一眼就能看到环境怎么配。
4.2 environment.yml 文件结构
一个标准的 environment.yml 长这样:
name: myproject
channels:
- conda-forge
- defaults
dependencies:
- python=3.9.13
- numpy=1.21.5
- pandas=1.4.2
- scikit-learn=1.0.2
- pip
- pip:
- flask==2.1.2
- gunicorn==20.1.0
几个关键字段:
- name——环境名称,创建时会直接用这个名字
- channels——指定包来源,优先级从上到下。我建议把
conda-forge放第一顺位 - dependencies——依赖列表。Conda 包直接写,pip 包放在
pip:子章节下
小提示:dependencies 里必须显式写上 pip 本身,否则 pip 章节的依赖不会被安装。这个坑我踩过一次,折腾了半小时才发现。
4.3 导出环境
导出环境最直接的命令就是:
conda env export -n myproject > environment.yml
这条命令会把环境中所有包——包括那些通过依赖关系自动安装的「隐式包」——全部写进文件。输出结果大概有上百行,因为很多底层的库(比如 libgcc、openssl)也会被列出来。
但这种全量导出有个问题。我记得有一次,我把 Linux 上导出的 environment.yml 发给 Windows 上的同事。结果他创建环境时报了一堆错——因为里面有很多 Linux 专属的依赖标识。解决方案?用 --from-history。
conda env export -n myproject --from-history > environment.yml
--from-history 只记录你显式安装过的包,那些自动依赖的底层库会被过滤掉。这样导出的文件干净、可读性强,跨平台兼容性也更好。
这两种导出方式的区别,我整理了一个表格:
| 对比维度 | conda env export | conda env export --from-history |
|---|---|---|
| 包的数量 | 全部(显式+隐式) | 仅显式安装的包 |
| 文件体积 | 大,通常 50–150 行 | 小,通常 10–30 行 |
| 跨平台兼容性 | 差(包含平台特有标识) | 好(只记录包名和版本) |
| 可读性 | 低 | 高 |
| 推荐场景 | 写论文、交付物需要精确锁死 | 团队协作、跨平台项目 |
核心建议:日常开发用 --from-history,发布交付件或写论文时用全量导出。我自己的项目一般两个文件都留一份——environment.yml 给开发者,environment.lock.yml 给生产环境。
4.4 从文件创建环境
拿到 environment.yml 之后,重建环境只需要一条命令:
conda env create -f environment.yml
它会自动读取 name 字段,创建同名环境。如果你想要自定义名字,可以再加 -n:
conda env create -f environment.yml -n myproject_v2
如果环境已经存在,想更新依赖,用 env update:
conda env update -f environment.yml -n myproject
注意,env update 不会删除环境中已有的、但不在 environment.yml 里的包。如果你想要「严格同步」,最好先删除旧环境再重新创建。
注意事项:执行 conda env create 前,最好先确认当前 Conda 版本是最新的。我在项目中遇到过因为 Conda 版本太旧,导致解析 environment.yml 时崩溃的情况。升级 Conda 就能解决。
4.5 跨平台兼容性处理
跨平台是环境复现里最头疼的问题之一。你想想看,Linux 上的 libopenblas 和 macOS 上的版本标识不一样,Windows 上甚至连路径分隔符都不同。直接把全量导出的 environment.yml 扔给同事,多半要出问题。
我的做法是:
- 用
--from-history导出主干依赖,保证跨平台可读 - 在文件里只写 Conda 包,尽量把 pip 依赖拆到单独的
requirements.txt - 显式锁定 Python 小版本,比如
python=3.9.13,避免不同平台拉到的 Python 补丁版本不一致
举个例子,这是我在一个跨平台项目中使用的 environment.yml:
name: myapp
channels:
- conda-forge
- defaults
dependencies:
- python=3.9.13
- numpy=1.21.5
- pandas=1.4.2
- scikit-learn=1.0.2
- pip
- pip:
- -r requirements.txt
这样 pip 依赖单独维护,requirements.txt 里可以精确锁死版本,而 Conda 部分只保留核心的、跨平台一致的包。
核心原则:environment.yml 里写的包越少,跨平台兼容性越好。只保留那些「必须精确锁死」的核心依赖,其他的交给 Conda 自动解析。
4.6 避坑指南
做了这么多次环境导出导入,我踩过几个值得说的坑:
- 导出文件里包含本地路径——有些版本的 Conda 会在
environment.yml里写入本地绝对路径,比如prefix: /home/user/anaconda3/envs/myproject。这个字段会干扰跨机器复现。我建议导出后手动检查,删掉prefix行。 - pip 依赖的版本冲突——Conda 和 pip 用的是两套依赖解析器。我曾经在
environment.yml里同时指定了numpy=1.21(Conda 侧)和numpy==1.19(pip 侧),结果环境创建成功但运行时报段错误。后来我统一把numpy写在了 Conda 侧,pip 侧只放纯 Python 包。 - 平台特有的构建标识——全量导出的文件里会出现类似
numpy=1.21.5=py39h5b5c0c7_0的标识,最后的h5b5c0c7是平台相关的 build hash。这种标识在其他平台上一定找不到对应包。所以跨平台分享时,务必用--from-history或者手动删除 build hash。
重要提醒:每次导出 environment.yml 后,我建议在另一台机器上做一次「空环境创建测试」——从零开始重建环境并跑一遍核心流程。这是验证可重复性的黄金标准。不要等到交给别人用了才发现文件有问题。
好了,关于环境导出与导入,核心内容就是这些。记住——environment.yml 是你的环境「DNA 序列」,保存好、管理好、验证好,可重复性就有了最坚实的第一步。