环境与依赖管理: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
我的建议: 如果项目涉及CUDA、cuDNN这些底层库,用Conda。它处理非Python依赖比pip强太多。我曾经被OpenCV的底层库依赖折磨过一整天,换了Conda后十分钟搞定。

依赖锁定:别再让「昨天还能跑」发生了

你有没有遇到过这种情况:项目跑得好好的,过了一周再拉代码,突然报错。查了半天,发现是某个依赖包悄悄升级了。

这就是依赖没有锁定的后果。

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.pyrequirements.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:
避坑指南: 我曾经在Docker里直接用了 latest 标签,结果某天Redis升级了不兼容的版本,整个服务挂了。现在我的习惯是:所有基础镜像都锁定具体版本号,比如 redis:7-alpine 而不是 redis:latest

知识体系总览

下面这张图把本章的核心逻辑串起来了。你可以看到从开发到部署,环境管理是怎么层层递进的。

环境与依赖管理:从开发到部署 开发环境隔离 Python虚拟环境 venv(轻量级,Python自带) Conda(数据科学,跨语言) 依赖锁定与管理 确保可复现的依赖环境 requirements.txt(传统方式) pyproject.toml(现代标准) 容器化部署 Docker:环境即代码,一次构建到处运行 Dockerfile(单服务构建) docker-compose(多服务编排) 核心目标:从「在我机器上能跑」到「在任何环境都能跑」 三层递进:隔离 → 锁定 → 容器化

总结一下

环境管理这件事,说白了就三个层次:

  1. 虚拟环境:解决Python包级别的隔离问题
  2. 依赖锁定:确保每次安装的依赖版本完全一致
  3. Docker化:解决操作系统级别的环境差异

这三层不是互斥的,而是层层递进的关系。我建议你从虚拟环境开始,逐步引入依赖锁定,最后再考虑Docker化。一口吃不成胖子,工程化也是逐步完善的。

嗯,这一章的内容就到这里。记住:环境管理不是锦上添花,而是工程化的基石。基础打好了,后面的路才走得稳。


专注资料整理