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 扔给同事,多半要出问题。

我的做法是:

  1. 用 --from-history 导出主干依赖,保证跨平台可读
  2. 在文件里只写 Conda 包,尽量把 pip 依赖拆到单独的 requirements.txt
  3. 显式锁定 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 序列」,保存好、管理好、验证好,可重复性就有了最坚实的第一步。