Skip to content

项目里混入 Python 脚本后,依赖该装在哪?venv 与 PEP 668 实战 ​

Python 脚本环境 venv 与 PEP 668 实战示意图

本文从一个真实报错切入,讲的不是"怎么修好这一条",而是多语言项目里 Python 依赖的边界该怎么划。 无论你的项目是 Node、Go、Rust 还是纯前端,只要混进了 .py 脚本,这篇都适用。

一、案发现场:两条命令,两个报错 ​

项目里有个 Python 检查脚本(scripts/*.py),本地开发时通过 npm script 串起来跑。某天敲下启动命令,第一环就挂了:

text
> python3 scripts/source-language-docs-check-colon.py

Traceback (most recent call last):
  File ".../scripts/source-language-docs-check-colon.py", line 5, in <module>
    import yaml
ModuleNotFoundError: No module named 'yaml'

第一反应是装一下就好了:

bash
python3 -m pip install pyyaml

结果撞上第二个报错:

text
error: externally-managed-environment

× This environment is externally managed
╰─> To install Python packages system-wide, try brew install
    xyz, where xyz is the package you are trying to
    install.

    If you wish to install a Python library that isn't in Homebrew,
    use a virtual environment:

    python3 -m venv path/to/venv
    source path/to/venv/bin/activate
    python3 -m pip install xyz

    If you wish to install a Python application that isn't in Homebrew,
    it may be easiest to use 'pipx install xyz', which will manage a
    virtual environment for you. You can install pipx with

    brew install pipx

    You may restore the old behavior of pip by passing
    the '--break-system-packages' flag to pip, or by adding
    'break-system-packages = true' to your pip.conf file. The latter
    will permanently disable this error.

    If you disable this error, we STRONGLY recommend that you additionally
    pass the '--user' flag to pip, or set 'user = true' in your pip.conf
    file. Failure to do this can result in a broken Homebrew installation.

    Read more about this behavior here: <https://peps.python.org/pep-0668/>

note: If you believe this is a mistake, please contact your Python installation or OS distribution provider. You can override this, at the risk of breaking your Python installation or OS, by passing --break-system-packages.
hint: See PEP 668 for the detailed specification.

一个简单的 import,被 Python 拦了两道。下面把这两道关拆开看。

二、第一坑:yaml 不是 Python 标准库 ​

看到 import yaml,直觉会以为它是内置模块,其实不是。Python 标准库里没有 yaml。

这里的 yaml 来自第三方包 PyYAML:

  • 安装名:pyyaml
  • 导入名:yaml

名字对不上,是很多人第一次装它时踩的坑。所以正确做法确实是 pip install pyyaml。真正的问题不在"装不装",而在装到哪个环境。

三、第二坑:PEP 668 与 "externally managed environment" ​

externally-managed-environment 这个报错来自 PEP 668。它的含义是:

这个 Python 环境由系统(如 Homebrew、Debian/Ubuntu 的 apt)管理,请不要往里面塞第三方包,以免破坏系统 Python。

这实际上是保护机制。过去很多人用 sudo pip install 把系统 Python 搞崩,陷入"升级就坏、不升级也坏"的循环。PEP 668 相当于给系统 Python 贴了封条。报错信息本身也把出路列全了:

  • 路径 A:brew install xyz,适用于 Homebrew 已打包的库
  • 路径 B:python3 -m venv ...,适用于任意第三方库
  • 路径 C:pipx install xyz,适用于命令行工具
  • 路径 D:--break-system-packages,逃生门,风险自负

四、核心问题:依赖到底该装在哪? ​

当项目里混入 Python 脚本,摆在面前的是四种装法,各有利弊:

装法命令适用场景风险
系统 Pythonsudo pip install不要用污染系统、升级即崩
用户级pip install --user个人机器上的小工具跨项目版本冲突
逃生门--break-system-packages临时救急破坏 Homebrew 管理
项目 venvpython3 -m venv .venv项目级依赖几乎无

结论:项目依赖放项目 venv。 这是唯一隔离、可重建、可复现的选项。

bash
cd /path/to/your-project

# 1. 创建虚拟环境
python3 -m venv .venv

# 2. 激活
source .venv/bin/activate

# 3. 安装依赖(注意在 venv 激活状态下)
pip install pyyaml

# 4. 验证
python3 -c "import yaml; print(yaml.__version__)"

成功后打印版本号(如 6.0.3)即完成。

五、为什么不用 --break-system-packages ​

最省事的做法显然是 python3 -m pip install --break-system-packages pyyaml,一行搞定,报错消失。但不要把它当首选,理由:

  1. 破坏 Homebrew 的管理契约:将来 brew upgrade python 可能冲突。
  2. 问题延迟爆发:今天不报错,不代表半年后不报。跨项目库版本互相干扰,是最难查的那类 bug。
  3. 不可复现:换台机器、换个同事,环境就不同。

venv 的成本只有三次命令加一行 .gitignore。用"可重建"换"省事",在项目级依赖上永远不亏。

六、一个细节:为什么不用改脚本 ​

你的 package.json(或 Makefile、shell 脚本)里调用的通常是裸的 python3:

json
{
  "scripts": {
    "check": "python3 scripts/some-check.py"
  }
}

venv 激活后,python3 会自动解析到 .venv/bin/python3,靠 PATH 优先级搞定。所以:

  • 不需要改脚本里的 python3 路径
  • 不需要设 PYTHONPATH
  • 只需要"先激活,再运行"
bash
source .venv/bin/activate
npm run docs:dev   # 或 make dev,或任何调用 python3 的命令

七、收尾:把经验固化下来 ​

修一次是运气,修成规范才是资产。三样东西值得固化进仓库:

1. .gitignore 忽略 venv ​

gitignore
.venv/

2. requirements.txt 固定版本 ​

警告

如果项目还没有 requirements.txt 文件,请先创建:

bash
touch requirements.txt

然后填入:

txt
pyyaml==6.0.3

3. README 补「首次配置」 ​

markdown
### 前置依赖(Python)

本项目部分脚本使用 Python,需创建虚拟环境:

```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```

可选:make dev 一键搞定 ​

makefile
dev:
	@if [ ! -d .venv ]; then python3 -m venv .venv && .venv/bin/pip install -r requirements.txt; fi
	@. .venv/bin/activate && npm run docs:dev

之后一句 make dev,venv 不存在会自动创建并装依赖,新人 clone 下来零思考。

八、可以带走的五条经验 ​

  1. import yaml 不是标准库。它来自 PyYAML,装包名与导入名不一致,别被名字骗。
  2. PEP 668 是好事。externally-managed-environment 是在保护系统 Python,遇到它该感谢,而不是想绕过。
  3. --break-system-packages 是最后手段。它存在,但不该是第一反应。
  4. venv 成本极低。三次命令加一行 .gitignore,就能永久解决"包装哪儿"的问题。
  5. 写完才算修完。把踩坑写成文档、README 或 make target,是团队收益最高的收尾动作。

一句话总结:再看到 ModuleNotFoundError: No module named 'xxx',先别急着 pip install,先问一句「这个依赖属于哪个项目?」。答案通常就是那个项目自己的 venv。

最后更新2026/11/12 14:52
如果你觉得这篇文章有帮助,或者想聊聊技术、工作,欢迎通过下面方式联系我:
contact fishfinal
京ICP备2024063630号-1