想象一下:你在项目 A 里用 Django 4.2,项目 B 因为遗留代码只能用 Django 3.2。如果全局只装一个版本的 Django,你只能在两个项目之间反复卸载重装——这简直是噩梦。

虚拟环境(Virtual Environment)就是来解决这个问题的:它为每个项目创建独立的 Python 环境,每个环境有自己的一套包,互不干扰。

为什么需要虚拟环境?

依赖冲突问题

没有虚拟环境时,你的全局 Python 环境是这样的:

# 全局安装的包(所有项目共享)
pip list
# Django 4.2
# requests 2.31
# numpy 1.26

这对不同项目的需求造成了冲突:

项目 需要的版本 问题
项目 A Django 4.2
项目 B Django 3.2 ❌ 全局装了 4.2
项目 C requests 2.20 ❌ 全局装了 2.31

虚拟环境的解决方案

系统 Python
├── 虚拟环境 A    → Django 4.2, requests 2.31
├── 虚拟环境 B    → Django 3.2, requests 2.20
├── 虚拟环境 C    → numpy 1.26, pandas 2.0
└── 全局环境      → pip, setuptools, wheel(基础工具)

每个虚拟环境是独立的目录,包含自己的 python 解释器副本和 site-packages 目录。包安装到虚拟环境中不影响系统或其他环境。

使用 venv(内置方案)

Python 3.3+ 内置了 venv 模块,无需额外安装。

创建虚拟环境

# 创建虚拟环境(会在当前目录创建 venv 文件夹)
python -m venv venv

# 或者指定名称
python -m venv .venv

通常约定使用 venv.venv 作为虚拟环境目录名,它会被添加到 .gitignore 中。

激活虚拟环境

# Windows(CMD)
venv\Scripts\activate

# Windows(PowerShell)
venv\Scripts\Activate.ps1

# macOS / Linux
source venv/bin/activate

激活后,命令行提示符会显示环境名称:

(venv) C:\Users\me\project>

此时 pythonpip 都会指向虚拟环境中的版本。

停用虚拟环境

deactivate

安装包

# 激活环境后
pip install requests
pip install django==4.2
pip install "numpy>=1.26,<2.0"

删除虚拟环境

# 直接删除目录即可
rm -rf venv    # macOS/Linux
rmdir /s venv  # Windows

然后重新创建——一切从零开始。

requirements.txt 锁定依赖

当项目需要共享时,需要记录所有依赖:

# 将所有依赖导出到文件
pip freeze > requirements.txt

生成的 requirements.txt 内容:

certifi==2024.7.4
charset-normalizer==3.3.2
django==4.2.16
idna==3.7
requests==2.31.0
sqlparse==0.5.1
urllib3==2.2.2

其他开发者拿到项目后:

# 1. 创建并激活虚拟环境
python -m venv venv
source venv/bin/activate    # macOS/Linux
# venv\Scripts\activate     # Windows

# 2. 一键安装所有依赖
pip install -r requirements.txt

requirements.txt 的最佳实践

可以按照用途分类多个文件:

# requirements.txt(生产依赖)
django>=4.2,<5.0
requests>=2.31
gunicorn>=22.0

# requirements-dev.txt(开发依赖)
-r requirements.txt         # 包含生产依赖
pytest>=8.0
pytest-cov>=5.0
black>=24.0
ruff>=0.5.0
mypy>=1.10
# 安装所有开发依赖
pip install -r requirements-dev.txt

依赖全量固定的最佳实践

使用 pip freeze 导出版本锁定的文件,与手写的宽松版本分开:

# requirements.in(手写,宽松版本)
django
requests
psycopg2-binary

# requirements.txt(自动生成,精确锁定)
# 用 pip freeze 或 pip-compile 生成

pip-tools:依赖版本锁定

pip-tools 帮你管理依赖的依赖——它能解析所有传递依赖,并锁定精确版本:

pip install pip-tools
# 1. 手写 requirements.in(只写直接依赖)
echo "django>=4.2" > requirements.in
echo "requests" >> requirements.in

# 2. pip-compile 解析所有依赖,生成精确锁定的 requirements.txt
pip-compile requirements.in

# 3. 用生成的 requirements.txt 安装
pip-sync requirements.txt

pip-compile 生成的 requirements.txt 包含了所有传递依赖的精确版本:

# This file is autogenerated by pip-compile with Python 3.13
# by the following command:
#    pip-compile requirements.in
#
asgiref==3.8.1
    # via django
certifi==2024.7.4
    # via requests
charset-normalizer==3.3.2
    # via requests
django==5.1.1
    # via -r requirements.in
idna==3.7
    # via requests
requests==2.31.0
    # via -r requirements.in
urllib3==2.2.2
    # via requests

pip-sync 不仅安装新包,还会卸载环境中不在 requirements.txt 里的包,确保环境完全同步。

Poetry:现代化包管理

Poetry 是一个集依赖管理、打包、发布于一体的工具。

安装 Poetry

# 推荐方式
pipx install poetry

# 或用官方安装脚本
curl -sSL https://install.python-poetry.org | python3 -

创建新项目

# 创建新项目
poetry new myproject

# 在已有项目中初始化
poetry init

pyproject.toml

Poetry 使用 pyproject.toml 替代 requirements.txt

[project]
name = "myproject"
version = "0.1.0"
description = "一个示例项目"
requires-python = ">=3.10"
dependencies = [
    "django>=4.2,<5.0",
    "requests>=2.31",
]

[project.optional-dependencies]
dev = [
    "pytest>=8.0",
    "black>=24.0",
]

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

Poetry 的核心命令

# 安装所有依赖
poetry install

# 只安装生产依赖
poetry install --only main

# 添加依赖
poetry add requests
poetry add --group dev pytest

# 移除依赖
poetry remove requests

# 更新依赖到最新版本(在版本约束范围内)
poetry update

# 显示依赖树
poetry show --tree

# 生成 requirements.txt(给不使用 Poetry 的人)
poetry export -f requirements.txt --output requirements.txt

虚拟环境的自动管理

Poetry 默认自动创建和管理虚拟环境:

# 查看当前使用的虚拟环境信息
poetry env info

# 手动指定 Python 版本创建虚拟环境
poetry env use python3.13

# 列出项目的所有虚拟环境
poetry env list

# 删除虚拟环境
poetry env remove --all

uv:极速替代方案

uv 是使用 Rust 编写的 Python 包管理工具,速度比 pip 快 10-100 倍:

# 安装 uv
pip install uv

# uv 可以作为 pip 的直接替代品
uv pip install requests

# 创建虚拟环境
uv venv

# 从 requirements.txt 安装
uv pip install -r requirements.txt

# 编译依赖
uv pip compile requirements.in -o requirements.txt

# 同步环境
uv pip sync requirements.txt

uv 的特点:

  • 速度极快:Rust 实现,比 pip 快 10-100 倍
  • 完全兼容:语法和 pip 几乎一样,迁移成本极低
  • 全局缓存:跨项目共享包缓存,节省磁盘空间
  • 无锁竞争:多进程安装互不阻塞
# 用 uv 初始化项目
uv init myproject
cd myproject

# 添加依赖
uv add requests
uv add --dev pytest

# 运行命令
uv run python -m pytest

Conda:数据科学的首选

Conda 是 Anaconda 生态的包管理工具,主要面向数据科学和机器学习。

# 创建环境
conda create -n myenv python=3.13

# 激活环境
conda activate myenv

# 安装包(Conda 仓库)
conda install numpy pandas scikit-learn

# 安装包(PyPI)
pip install some-package

# 导出环境
conda env export > environment.yml

# 从文件创建环境
conda env create -f environment.yml

Conda 的特点:

  • 跨语言:不仅管理 Python 包,还能管理 C/C++ 库、R 包等
  • 二进制包:预编译的二进制文件,不需要编译工具链
  • 数据科学生态:numpy、pandas、scikit-learn 等的安装体验极佳

但 Conda 的缺点也很明显:仓库更新比 PyPI 慢,且环境解析速度较慢。

依赖解析问题

什么是依赖地狱?

项目要求:
├── package-a==1.0
│   └── requires: package-c>=2.0
├── package-b==2.0
│   └── requires: package-c<2.0
└── → package-c 无法满足两个约束!

这就是依赖冲突——两个包对同一个传递依赖有互不兼容的版本要求。

pip vs Poetry 的依赖解析

# pip 采用贪心解析——可能安装出有冲突的版本
pip install package-a package-b
# 成功安装了……但运行时报错

# Poetry 采用 SAT 求解器——安装前就检测冲突
poetry add package-a package-b
# 如果冲突,直接报错并告诉你原因

解决依赖冲突的策略

  1. 升级/降级:看看有没有其他版本能兼容所有约束
  2. 更换替代包:如果 package-a 长期不更新,考虑找替代者
  3. 解耦:将两个不兼容的功能拆到不同的项目中
  4. 使用容器:Docker 容器各自有独立的环境

虚拟环境的最佳实践

1. 永远使用虚拟环境

# 不要这样做
pip install django   # 全局安装!污染系统环境

# 应该这样做
python -m venv venv
source venv/bin/activate
pip install django   # 在虚拟环境中安装

2. .gitignore 中加入虚拟环境目录

# .gitignore
venv/
.venv/
__pycache__/
*.pyc
.env

不要将虚拟环境提交到 Git——它是机器相关的,每个人应该自己创建。

3. 精确锁定版本并提交

# 生成锁文件并提交到 Git
pip freeze > requirements.txt
git add requirements.txt

这样所有人都能安装完全相同的版本,避免"在我机器上能跑"的尴尬。

4. 开发与生产依赖分离

# 安装生产环境依赖(线上服务器)
pip install -r requirements.txt

# 安装开发环境依赖(本地开发)
pip install -r requirements-dev.txt

5. 定期更新依赖

# 检查过期的包
pip list --outdated

# 更新单个包
pip install --upgrade requests

# 用 pur 批量更新(需要 pip install pur)
pur -r requirements.txt

6. 使用 pipx 隔离命令行工具

pipx 将 Python 命令行工具安装在隔离环境中,避免干扰项目依赖:

pip install pipx

# 安装工具——每个工具在自己的虚拟环境中
pipx install black
pipx install poetry
pipx install ruff
pipx install pytest

# 运行但不永久安装
pipx run cowsay "Hello"

# 列出已安装的工具
pipx list

实战:用 Poetry 构建项目

让我们完整地走一遍 Poetry 的项目创建流程:

# 1. 安装 Poetry
pipx install poetry

# 2. 创建新项目
poetry new task-cli
cd task-cli

生成的项目结构:

task-cli/
├── pyproject.toml
├── README.md
├── src/
│   └── task_cli/
│       └── __init__.py
└── tests/
    ├── __init__.py
    └── test_task_cli.py
# 3. 添加依赖
poetry add click
poetry add --group dev pytest pytest-cov black ruff

# 4. 激活虚拟环境
poetry shell

# 5. 运行测试
poetry run pytest

# 6. 查看依赖树
poetry show --tree

生成的 pyproject.toml

[project]
name = "task-cli"
version = "0.1.0"
description = "一个简单的任务管理 CLI 工具"
requires-python = ">=3.10"
dependencies = [
    "click>=8.1",
]

[project.optional-dependencies]
dev = [
    "pytest>=8.0",
    "pytest-cov>=5.0",
    "black>=24.0",
    "ruff>=0.5.0",
]

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

[tool.black]
line-length = 88

[tool.ruff]
line-length = 88
target-version = "py310"

[tool.pytest.ini_options]
testpaths = ["tests"]

工具选型指南

场景 推荐工具 理由
标准库方案 venv + pip 零依赖,适合简单项目
需要版本锁定 pip-tools 传递依赖精确锁定
新项目/库开发 Poetry 一站式方案,兼顾依赖和打包
追求速度 uv Rust 实现,极速体验
数据科学/ML Conda 预编译二进制,跨语言支持
安装 CLI 工具 pipx 隔离命令行工具

小结

本章我们学习了 Python 虚拟环境与依赖管理的核心知识:

  • 虚拟环境的意义:隔离项目依赖、避免版本冲突、保持系统整洁
  • venv:Python 内置,python -m venv venv 创建,activate 激活
  • requirements.txtpip freeze 导出,pip install -r 安装,开发/生产分离
  • pip-toolspip-compile 解析依赖树,pip-sync 同步环境
  • Poetrypyproject.toml 一站式管理,自动虚拟环境,SAT 求解器解析依赖
  • uv:Rust 编写的极速 pip 替代品,完全兼容 pip 语法
  • Conda:数据科学场景的首选,跨语言包管理
  • 最佳实践:始终使用虚拟环境,锁定版本并提交,开发/生产分离,定期更新

记住:虚拟环境不是可选项,是每一个 Python 项目的必需品。无论项目大小,先创建虚拟环境再装包——这个习惯会让你的开发之路顺畅很多。

Summary: 虚拟环境、venv、pip-tools、Poetry、uv 与依赖管理实践。