环境与依赖管理:Python虚拟环境、依赖锁定与Docker化基础
说实话,我见过太多项目死在「环境不一致」这个坑里。
本地跑得好好的,一上服务器就报错。同事拉下来代码,第一件事就是折腾半天环境。这种痛苦,做过工程化的人应该都懂。
所以这一章,咱们就把环境与依赖管理这件事彻底讲透。从虚拟环境到依赖锁定,再到Docker化,一条线串起来。
Python虚拟环境:为什么你需要它?
先问个问题:你电脑上是不是装了一堆Python包?
TensorFlow 2.x 和 1.x 冲突过吗?Django 3 和 Django 4 同时需要过吗?
这就是问题所在。全局环境就是个公共厨房,谁都能用,但谁都不收拾。
虚拟环境就是给每个项目一个独立的厨房。 你爱怎么折腾都行,不影响别人。
venv:Python自带的轻量方案
Python 3.3 之后自带了 venv,不需要额外安装。我个人习惯用它做快速原型。
# 创建虚拟环境
python -m venv myenv
# 激活(Windows)
myenv\Scripts\activate
# 激活(macOS/Linux)
source myenv/bin/activate
# 退出
deactivate
嗯,这里要注意:venv 创建的虚拟环境默认不包含 pip 以外的包管理工具。如果你需要 conda 那种更强大的包管理能力,往下看。
Conda:数据科学家的首选
我在做AI项目时,几乎都用Conda。为什么?因为它不仅能管理Python包,还能管理Python版本本身。
# 创建环境并指定Python版本
conda create -n agent_project python=3.10
# 激活
conda activate agent_project
# 安装包
conda install numpy pandas
# 导出环境
conda env export > environment.yml
# 从文件创建环境
conda env create -f environment.yml
依赖锁定:别再让「昨天还能跑」发生了
你有没有遇到过这种情况:项目跑得好好的,过了一周再拉代码,突然报错。查了半天,发现是某个依赖包悄悄升级了。
这就是依赖没有锁定的后果。
requirements.txt:最传统的方式
这是最基础的做法,但很多人用错了。
# 错误做法:只写包名
numpy
pandas
flask
# 正确做法:锁定版本
numpy==1.24.3
pandas==2.0.3
flask==2.3.2
requests>=2.28.0, <3.0.0 # 版本范围约束
生成锁定文件也很简单:
# 导出当前环境的精确版本
pip freeze > requirements.txt
# 从锁定文件安装
pip install -r requirements.txt
pip freeze 会导出所有已安装的包,包括依赖的依赖。这会导致文件冗余。更好的做法是用 pipreqs 只导出项目直接依赖。
pyproject.toml:现代Python的标准
如果你关注Python社区的最新动态,会发现 pyproject.toml 正在成为标准。PEP 517/518 之后,它逐渐取代了 setup.py 和 requirements.txt。
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "my-agent-project"
version = "0.1.0"
description = "一个AI Agent工程化项目"
requires-python = ">=3.10"
dependencies = [
"openai>=1.0.0",
"langchain>=0.1.0",
"pydantic>=2.0.0",
"redis>=5.0.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7.0",
"black>=23.0",
"ruff>=0.1.0",
]
test = [
"pytest-cov>=4.0",
"pytest-asyncio>=0.21",
]
使用 pyproject.toml 的好处很明显:
- 语义清晰:直接依赖和间接依赖分开管理
- 分组管理:开发依赖、测试依赖、生产依赖分开
- 工具统一:black、ruff、pytest 的配置都可以放进来
安装方式也很简单:
# 安装生产依赖
pip install .
# 安装开发依赖
pip install -e ".[dev]"
# 安装所有依赖
pip install -e ".[dev,test]"
Docker化基础:从「在我机器上能跑」到「在哪都能跑」
虚拟环境解决了Python层面的隔离问题,但操作系统层面的差异呢?
比如你的项目依赖了某个C库,或者需要特定版本的CUDA。这时候Docker就派上用场了。
Dockerfile:你的环境即代码
一个典型的AI Agent项目Dockerfile长这样:
# 使用官方Python镜像作为基础
FROM python:3.10-slim
# 设置工作目录
WORKDIR /app
# 设置环境变量
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1
# 先复制依赖文件,利用Docker缓存
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 复制项目代码
COPY . .
# 暴露端口
EXPOSE 8000
# 启动命令
CMD ["python", "main.py"]
这里有个小技巧:先复制依赖文件,再复制代码。这样依赖没变时,Docker会复用缓存层,构建速度快很多。
多阶段构建:让镜像更小
我在生产环境中常用多阶段构建。为什么?因为基础镜像动不动就1个多G,传到镜像仓库太慢了。
# 第一阶段:构建阶段
FROM python:3.10-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
# 第二阶段:运行阶段
FROM python:3.10-slim
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY . .
ENV PATH=/root/.local/bin:$PATH
EXPOSE 8000
CMD ["python", "main.py"]
这样最终镜像只包含运行所需的最小文件,体积能减少60%以上。
docker-compose.yml:多服务编排
Agent项目通常不止一个服务。比如你需要Redis做缓存,PostgreSQL做持久化。这时候用 docker-compose 最方便。
version: '3.8'
services:
agent:
build: .
ports:
- "8000:8000"
environment:
- REDIS_URL=redis://redis:6379
- DATABASE_URL=postgresql://user:pass@db:5432/agent
depends_on:
- redis
- db
volumes:
- ./data:/app/data
redis:
image: redis:7-alpine
ports:
- "6379:6379"
db:
image: postgres:15-alpine
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=pass
- POSTGRES_DB=agent
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
latest 标签,结果某天Redis升级了不兼容的版本,整个服务挂了。现在我的习惯是:所有基础镜像都锁定具体版本号,比如 redis:7-alpine 而不是 redis:latest。
知识体系总览
下面这张图把本章的核心逻辑串起来了。你可以看到从开发到部署,环境管理是怎么层层递进的。
总结一下
环境管理这件事,说白了就三个层次:
- 虚拟环境:解决Python包级别的隔离问题
- 依赖锁定:确保每次安装的依赖版本完全一致
- Docker化:解决操作系统级别的环境差异
这三层不是互斥的,而是层层递进的关系。我建议你从虚拟环境开始,逐步引入依赖锁定,最后再考虑Docker化。一口吃不成胖子,工程化也是逐步完善的。
嗯,这一章的内容就到这里。记住:环境管理不是锦上添花,而是工程化的基石。基础打好了,后面的路才走得稳。