Python 项目中的很多“代码问题”,实际上是环境问题:终端启动了不同的 shell、python 指向了意外的解释器、依赖装进了全局环境,或者 pip 与当前 Python 根本不是同一套安装。要建立一个稳定、可复现的开发环境,关键不是记住更多命令,而是分清每层工具的职责。
先画清环境的四个层次
一个典型的 Python 开发环境可以拆成四层:
| 层次 | 常见工具 | 主要职责 |
|---|---|---|
| 交互入口 | Terminal、Windows Terminal、iTerm 等 | 显示输入输出,承载命令行会话 |
| 命令解释 | Bash、Zsh、PowerShell | 解析命令、变量、管道和启动脚本 |
| Python 版本 | pyenv、系统 Python | 决定使用 Python 3.10、3.11 还是其他版本 |
| 项目与依赖 | venv、pip、Poetry |
隔离项目环境,安装并记录第三方包 |
终端和 shell 并不是同一个东西。终端是运行 shell 的界面,shell 才负责解释 cd、环境变量和脚本。更换终端应用通常不会自动改变 Python;更换 shell 或它的启动配置,却可能改变 PATH,进而让 python 指向另一套解释器。
可以用下面这些命令检查当前会话究竟在使用什么:
printf 'shell=%s\n' "$SHELL"
command -v python
python --version
python -c 'import sys; print(sys.executable)'
python -m pip --version
最后两行尤其重要。sys.executable 给出当前解释器的真实路径,python -m pip --version 则会显示 pip 所属的位置。两者应当落在同一个预期环境中。
pyenv 与虚拟环境解决的是不同问题
pyenv 主要用于选择 Python 解释器版本。虚拟环境则基于某个解释器,为单个项目创建隔离的安装目录。它们可以配合,但不能相互替代。
可以这样实践一个普通项目环境。以下示例假设已经安装 pyenv,并且系统提供 Bash 或 Zsh:
mkdir environment-demo
cd environment-demo
pyenv install -s 3.12.3
pyenv local 3.12.3
python -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install requests
python -c 'import sys, requests; print(sys.executable); print(requests.__version__)'
Windows PowerShell 中,激活命令通常改为:
.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
这里的执行顺序很关键:先让 pyenv 选择基础解释器,再用这个解释器创建 .venv。创建以后,虚拟环境不会随着 .python-version 的修改自动换成另一个 Python 版本。需要升级基础版本时,通常应重新创建虚拟环境并重新安装依赖。
.venv 目录不应提交到版本库,可以在 .gitignore 中加入:
.venv/
__pycache__/
*.py[cod]
pip 与 Poetry:选择一种清晰的依赖工作流
pip 负责把包安装进某个 Python 环境。它很基础,也很可靠,但项目需要自行决定如何记录直接依赖、锁定版本和构建发布包。
一个小型应用可以采用 venv、pip 和需求文件:
python -m venv .venv
source .venv/bin/activate
python -m pip install requests==2.32.3
python -m pip freeze > requirements.txt
python -m pip install -r requirements.txt
需要注意,pip freeze 会记录当前环境中的全部已安装包,包括间接依赖。它适合生成可重复安装的环境快照,但不一定适合作为人工维护的顶层依赖清单。
Poetry 把依赖声明、锁文件、虚拟环境和项目打包整合到同一套工作流中。可以这样建立一个最小项目:
mkdir poetry-demo
cd poetry-demo
poetry init --no-interaction
poetry add requests
poetry run python -c 'import requests; print(requests.__version__)'
poetry env info
团队采用 Poetry 时,应提交 pyproject.toml 和锁文件,而不是提交虚拟环境目录。运行命令时使用 poetry run ...,可以减少忘记激活环境造成的歧义。
不要在同一个项目里无规则地混用 pip freeze、手写 requirements.txt 和 Poetry 锁文件。工具并非不能共存,但必须明确哪个文件是依赖的权威来源,否则本地环境与持续集成环境很容易产生差异。
用故障现象反查环境层次
遇到 ModuleNotFoundError 时,不要立即再次执行 pip install。先收集解释器与安装位置:
python -c 'import sys; print(sys.executable)'
python -m pip --version
python -m pip show requests
python -c 'import site; print(*site.getsitepackages(), sep="\n")'
常见判断方式如下:
python与pip路径不属于同一环境:改用python -m pip。- 激活环境后路径仍指向系统 Python:检查 shell 的
PATH、别名和启动脚本。 - pyenv 版本正确,但包仍然缺失:包可能装在另一个虚拟环境中。
- 本地可以运行,CI 无法运行:检查是否提交了依赖声明和锁文件,以及 CI 使用的 Python 版本。
- IDE 与终端行为不同:IDE 可能选择了另一条解释器路径,需要在项目设置中显式指定
.venv。
一个简短的自测
在不执行命令的情况下,先回答下面几个问题:
- 更换终端应用是否必然改变 Python 版本?不必然,真正影响解析结果的是 shell 配置与
PATH等环境设置。 - pyenv 选择 Python 3.12 后,旧
.venv是否自动升级?不会,虚拟环境通常需要重建。 - 为什么推荐
python -m pip?因为它明确要求当前python解释器运行对应的 pip 模块。 - 是否应该提交
.venv?通常不应提交;应提交依赖声明和必要的锁文件。 - Poetry 项目是否必须先手动激活环境?不一定,可以使用
poetry run在项目环境中执行命令。
建立可预测环境的检查清单
开始编码前,确认 python --version 与项目要求一致,并通过 sys.executable 检查解释器路径。每个项目使用独立虚拟环境,只选择一套主要依赖管理流程,并把版本声明与锁文件纳入版本控制。
排查问题时,从终端、shell、PATH、Python 解释器、虚拟环境到包安装位置逐层确认。环境管理的目标不是堆叠更多工具,而是让每位开发者和 CI 都能明确回答两个问题:现在运行的是哪个 Python,以及依赖究竟安装在哪里。