接手一个Python项目时,最让人头疼的莫过于环境配置问题。我遇到过无数次这样的情况:从GitHub克隆了一个看起来很酷的项目,结果在安装依赖时卡了整整两天。这种经历让我深刻理解到,环境复现是每个Python开发者必须掌握的生存技能。
requirements.txt和environment.yml就像两份不同的"食谱",前者是家常菜做法(pip系),后者是分子料理指南(conda系)。新手最容易犯的错误就是混用这两套系统,比如用conda安装requirements.txt,结果遇到各种奇怪的报错。最近在复现一个机器学习项目时,我就因为这个问题浪费了三个小时。
这两种文件的本质区别在于:requirements.txt只记录Python包信息,而environment.yml保存的是完整的conda环境快照。这就好比搬家时,前者只打包了家具清单,后者连房间布局都原样复制了。理解这个差异,是避免踩坑的第一步。
生成requirements.txt的标准姿势是使用pip freeze命令。但这里有个坑:这个命令会导出当前环境的所有包,包括你不想要的测试依赖。我推荐使用pipreqs这个神器,它能智能分析项目实际import的包:
bash复制pip install pipreqs
pipreqs /path/to/project --force
安装依赖时,新手常直接运行pip install -r requirements.txt。但在生产环境中,更好的做法是:
bash复制python -m pip install --upgrade pip
python -m pip install -r requirements.txt --no-cache-dir
--no-cache-dir能避免使用缓存的旧版本包,这在Docker构建时特别有用。去年我在部署一个Django项目时,就因为这个缓存问题导致线上环境用了错误的包版本。
基础的requirements.txt长这样:
code复制numpy==1.21.0
pandas>=1.3.0
但实际项目中你可能需要更复杂的语法:
code复制-e git+https://github.com/user/repo.git@branch#egg=package
./local_package/
--extra-index-url https://private.pypi/simple
trusted-host=private.pypi
特别要注意的是Git依赖项(-e开头的那行)。上个月我在复现一个NLP项目时,就因为漏掉了这个标记导致安装失败。这种依赖项通常指向项目特定的fork版本,用普通pip install无法替代。
创建environment.yml的正确姿势是:
bash复制conda env export --no-builds > environment.yml
--no-builds参数非常关键,它能去掉平台特定的构建号。有次我忘记加这个参数,结果在Linux导出的环境无法在Mac上复现。
一个典型的environment.yml包含这些要素:
yaml复制name: my_env
channels:
- defaults
- conda-forge
dependencies:
- python=3.8
- numpy=1.21
- pip:
- torch==1.9.0
注意pip安装的包要单独列出。我建议尽量使用conda源,因为conda能处理非Python依赖。比如OpenCV在conda里是单个包,用pip安装则需要额外配置系统依赖。
conda环境移植最常见的问题是平台差异。经过多次踩坑,我总结出这些经验:
yaml复制platforms:
- linux-64
- osx-64
bash复制conda config --append channels conda-forge
conda env update --file environment.yml
yaml复制dependencies:
- pip=21.3
- pip:
- some-package==1.0
| 特性 | requirements.txt | environment.yml |
|---|---|---|
| 依赖解析器 | pip | conda |
| 非Python依赖支持 | 不支持 | 支持 |
| 虚拟环境管理 | 需额外工具 | 内置支持 |
| 平台兼容性 | 较好 | 需特殊处理 |
| 安装速度 | 较快 | 较慢 |
| 依赖冲突解决 | 基础 | 高级 |
根据我的经验,数据科学项目首选conda,Web开发项目用pip更简单。但有一种特殊情况:当项目同时包含这两种文件时,应该优先使用environment.yml,因为它通常更完整。
有些项目需要同时使用conda和pip。这种情况下,我的标准操作流程是:
bash复制conda create -n hybrid_env python=3.9
conda activate hybrid_env
bash复制conda install --file conda_requirements.txt
bash复制pip install -r pip_requirements.txt
记得在environment.yml中记录所有pip安装的包,否则其他人复现环境时会漏掉这些依赖。我在团队协作项目中专门写了pre-commit钩子来检查这一点。
遇到"Could not find a version that satisfies the requirement"错误时,不要急着降级包版本。我通常这样做:
--use-deprecated=legacy-resolver选项对于conda环境,可以尝试:
bash复制conda update --all
conda clean --all
conda install --freeze-installed package_name
确保环境复现成功的黄金法则是实际运行测试用例。我习惯这样做验证:
如果项目没有测试,至少应该运行主要入口点。有次我自以为环境配置成功了,结果在调用特定模块时才发现缺少一个隐式依赖。
对于需要绝对复现性的场景(比如论文实验),我推荐使用Docker。这是我在用的模板:
dockerfile复制FROM continuumio/miniconda3
WORKDIR /app
COPY environment.yml .
RUN conda env create -f environment.yml
RUN echo "conda activate my_env" >> ~/.bashrc
ENV PATH /opt/conda/envs/my_env/bin:$PATH
构建时使用固定版本的base镜像,能最大限度保证可复现性。我在学术论文复现中使用这个方法,成功率接近100%。
在CI/CD流程中,我推荐这样配置:
yaml复制jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: conda-incubator/setup-miniconda@v2
with:
environment-file: environment.yml
activate-environment: my_env
- run: pytest
对于纯pip项目,可以使用cachix加速构建:
yaml复制- uses: actions/cache@v2
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
这些配置能显著减少CI运行时间。在我们团队的实践中,构建时间从15分钟缩短到了3分钟。
在维护多个开源项目过程中,我总结出这些血泪教训:
numpy>=1.19,<2.0requirements-dev.txtpip check命令检测冲突最近我接手的一个老项目,requirements.txt里居然有32个间接依赖。通过逐步清理和版本锁定,最终将依赖树精简到了12个直接依赖,构建时间缩短了60%。