本文目录
终端里 pip install httpx 显示 Successfully installed,脚本里 import httpx 却报 ModuleNotFoundError——这类问题多半不是包坏了,而是装进了 A 环境、却用 B 环境的 python 在跑。第三方依赖、虚拟环境、安装工具三者的边界理清,比记住更多 pip 子命令管用。
系列第一篇讲过解释器与 venv 概念;这一篇把视线移到依赖从哪来、装到哪、怎么写进清单,以及协作时如何复现同一套 site-packages。
标准库 vs 第三方:谁需要 pip
解释器自带的 import json、import pathlib 属于标准库,随 Python 发行版安装。PyPI 上的 httpx、pytest、django 等是第三方包,必须对当前要运行代码的那个环境执行安装:
import json # 标准库
import httpx # 第三方,需先 pip install httpxpip list 列的是「这个环境」里有的包,不是整台机器的历史清单。同一台机器上可以同时存在系统 Python、pyenv 版本、多个项目的 .venv,各自 site-packages 互不相通。
pip 与 PyPI:下载与安装
pip 从 PyPI(Python Package Index)等源拉取发行版(wheel 或 sdist),解压到环境的 site-packages,并写入元数据:
python -m pip install httpx==0.27.0
python -m pip show httpx
python -c "import httpx; print(httpx.__version__)"习惯写 python -m pip 而不是裸 pip,是为了确保 pip 绑定到你在用的那个解释器。macOS / Linux 上裸 pip 有时指向 Python 2 或系统工具链,装完却 import 失败。
升级、卸载同样要带上 -m:
python -m pip install --upgrade httpx
python -m pip uninstall httpxvenv:依赖装进各自的房间
venv 创建隔离环境:独立的 python 与 site-packages,项目之间互不污染:
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
python -m pip install httpx
deactivate激活后,提示符前常见 (.venv),此后 which python 应指向 .venv/bin/python。.venv 一般加入 .gitignore——体积大、可重建;协作者克隆仓库后自己 venv + pip install -r requirements.txt。
未激活 venv 时,仍可用绝对路径调用隔离解释器:
.venv/bin/python -m pip install httpx
.venv/bin/python script.pyCI 与生产部署里常见这种「不 activate、直接指路径」的写法,避免 shell 状态依赖。
requirements.txt:记录装过什么
requirements.txt 是依赖清单,一行一个包(可带版本约束):
httpx>=0.27,<0.28
pytest>=8.0python -m pip install -r requirements.txt
python -m pip freeze > requirements-lock.txtfreeze 会列出当前环境全部已装包(含传递依赖),直接提交往往过细。团队更常:手写主依赖 + 版本范围,或用 pip-tools / uv 生成锁文件,保证 CI 与本地一致。
清单只解决「装什么」;在哪装仍取决于你激活了哪个 venv、CI job 里选了哪个 python。新同事最常见的坑是:pip install -r requirements.txt 跑在系统 Python 上,项目 .venv 里依旧缺包。
验证当前环境:几行 Python
排错时,用脚本确认「这个解释器能不能看到包」比猜路径快:
import sys
print("executable:", sys.executable)
print("prefix:", sys.prefix)
try:
import httpx
print("httpx:", httpx.__version__)
except ModuleNotFoundError:
print("httpx not in this environment")sys.executable 应是你在 IDE / 终端里期望的那一个;sys.prefix 在 venv 里通常指向 .venv 目录。
排错 checklist
which python/where python:是否指向项目的.venv?python -m pip show 包名:这个解释器能看到该包吗?- 包名 vs import 名:PyPI 上叫
Pillow,import 是PIL;python-dotenvimport 是dotenv。 - IDE 解释器设置:VS Code / PyCharm 里选的内核可能与终端不是同一个。
- 权限与
--user:pip install --user装进用户目录,与 venv 策略混用易乱。
第三方库扩展了工程能力,但安装上下文错了,代码一行都跑不起来。把「当前用的是哪个 python」当成每次装包、每次排错的第一问题,能省掉大量无效重试。下一批系列会进入异步与并发,那些示例同样假设:依赖已经装进正在运行脚本的那个环境。
版本约束怎么写
requirements.txt 里常见写法:
httpx>=0.27,<0.28 # 允许补丁,排除下一大版
pytest==8.2.0 # 钉死,复现 CI 用
django~=4.2 # 兼容发布:>=4.2,<4.3过宽的 httpx 不带版本可能某天拉到 breaking release;过窄的钉死每一层传递依赖会让安全补丁难升。主依赖手写范围,锁文件管 CI 精确复现,是多数团队的折中。
PyPI 之外:私有源与镜像
企业内网常用私有索引;pip 通过 --index-url 或 pip.conf 配置。国内访问 PyPI 慢时,临时镜像:
python -m pip install httpx -i https://pypi.tuna.tsinghua.edu.cn/simple镜像只改变下载位置,包装进 site-packages 后的 import 行为不变。仍要注意镜像同步延迟与 supply-chain 信任——生产环境更常用内网制品库。
editable 安装与开发期布局
本地包开发常用 pip install -e .(editable):源码改动立刻反映到 import,不必每次重装。前提是项目根有 pyproject.toml / setup.cfg 声明包名与包目录。这与「把项目根塞进 PYTHONPATH」目的一致,但路径由安装元数据管理,更少手工踩坑。
cd myproject
python -m venv .venv
source .venv/bin/activate
python -m pip install -e ".[dev]"常见误解
| 误解 | 实际 |
|---|---|
| pip install 装到「Python 里」 | 装到当前环境的 site-packages |
| 全局 pip 一次,所有项目能用 | 项目应各自 venv,或统一容器/镜像 |
| requirements 含版本就够了 | 还要在同一 python 上执行 install |
| 包名等于 import 名 | 查 PyPI 页面的 Import 说明 |
小结
PyPI 提供索引;pip 把发行版装进当前环境的 site-packages;venv 隔离各项目的依赖;requirements(或锁文件)记录「要装什么」。装包、跑脚本、配 IDE 时,三条线指向同一个 sys.executable,第三方库才算真正「可用」。
与容器、CI 的同一条线
本地 .venv、Docker 镜像、GitHub Actions 里的 setup-python,本质都在回答:跑这段代码时,site-packages 里有什么。Dockerfile 里常见:
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
COPY requirements.txt .
RUN pip install -r requirements.txtCI 里用 actions/setup-python 指定版本后,同样 pip install -r requirements.txt,再 pytest。若 CI 没 venv、直接用系统 python,只要版本与 path 一致也可以——关键是不要用和本地完全不同的两套规则而不文档化。
安全与 reproducibility(概念层)
从 PyPI 装包涉及供应链信任:优先认知名称、看下载量与项目主页、锁版本范围。pip install 默认不执行包的任意代码安装钩子以外的逻辑,但恶意包仍可能存在——企业环境常用私有镜像与审批流程。
reproducibility 靠:钉版本或锁文件 + 同一 Python 小版本 + 同一安装命令。新人 clone 仓库后的「黄金路径」应写在 README:创建 venv → 激活 → pip install -r requirements.txt → python -m pytest。少一步「我记得全局装过」,就少一类 import 玄学。
和 pyproject.toml 的关系
现代项目越来越多用 pyproject.toml 声明构建后端与依赖组([project.dependencies]、[project.optional-dependencies]),pip install . 或 pip install -e ".[dev]" 从元数据安装,而不只读 flat 的 requirements.txt。两者可以并存:pyproject 是源,requirements 由工具导出给 CI 用。理解 pip 始终操作的是当前环境,换 manifest 格式不改变「装到哪」这条物理规则。
多 Python 版本与依赖_wheel
某些包带 C 扩展,PyPI 上按平台发布不同 wheel 文件名(cp312、manylinux 等)。pip 会自动选与当前解释器 ABI 匹配的 wheel;若只有 sdist,本地需要编译链,安装变慢甚至失败。排错时看 pip install -v 日志里下载的文件名,能区分是「网络问题」还是「这个平台没有预编译包」。
在 pyenv 或容器里换 Python 小版本后,必须在新版本的环境里重装依赖——site-packages 目录按版本隔离,3.11 的 venv 里不会有 3.12 装的包,反之亦然。这也是「先 which python 再 pip」的另一层原因:版本与路径要对成对出现。
协作时把「创建 venv、安装依赖、跑测试」三条命令写进 README 或 Makefile,比口头传述可靠。新人第一天若卡在 import,九成是当前 python 与 pip 目标不一致,而不是 PyPI 宕机。
pip freeze 输出可存档备查,但别把它当唯一真相——传递依赖版本锁太死,安全补丁难升。更可持续的是:主依赖写在 pyproject 或 requirements.in,锁文件由工具生成,定期人工 review major 升级。
记住四个词的物理含义:PyPI 是目录;pip 是安装器;venv 是隔离的 site-packages 房间;requirements 是装什么包的清单。命令可以在不同机器上重复,只要 sys.executable 对齐。
requirements.txt 一行一个包,版本约束用比较运算符表达;与 pyproject.toml 可以并存,由团队选「单一真相源」。无论 manifest 长什么样,安装动作始终是:对当前解释器对应的 environment 执行 pip install。
第一次 clone 仓库的标准动作是:创建 venv → 激活 → pip install -r requirements.txt → 用同一 venv 里的 python 跑测试。跳过任一步,都可能再现「我明明 pip install 过了」的错觉。养成 python -m pip 与解释器路径对照的习惯,第三方依赖会从玄学变成可重复的工程步骤。