核心差异,一眼看清:
| | |
|---|
| | |
| package.json | pyproject.toml |
| | |
| | |
| | |
| | |
下面我们就从这张表出发,把背后的逻辑掰开揉碎聊清楚。

一、npm 凭什么成为“默认选项”
1. 与 Node.js 零门槛捆绑
Node.js 的安装包自带 npm。对开发者来说,装完 Node,npm 就已就位。不用额外选型、不用纠结配置,所有 JavaScript 项目都能跑 npm install。这种“开箱即得”的覆盖度,让 npm 天然成为受众最广的入口。
2. 全球最大的开源注册表
npm registry 上有超过 200 万个包,从前端框架到后端工具,从构建插件到 CLI 应用,几乎囊括一切。这个超大规模的中心化仓库形成极强的网络效应:
- 你想把自己的库分享出去,npm 是曝光度最高的地方。
3. 填补了语言本身的标准库缺口
JavaScript 语言的标准库其实相当薄弱。早期的 ES 规范里,连 Promise、Map 这种基础工具都没有,更别说文件系统、网络请求了。社区通过 npm 建设出了 lodash、axios、express 等“事实标准库”。npm 实际上承担了标准库的职能。
4. 工程化的唯一调度中心
这可能是最关键的一点。package.json 里的 scripts 字段,定义了项目的启动、构建、测试、发布等全部工作流。任何一个开发者克隆项目后,都知道敲 npm run dev 或 npm test。npm 已经变成了一个内置的任务运行器,把项目管理、依赖、构建、发布全部收拢到一个入口下。
这意味着,对一个开源项目来说,选 npm 不是选了一个包管理器,而是选了一套完整的、零配置的工程化基座。
二、Python 也有庞大的库
没错,PyPI 上的库同样多到惊人。但“库多”只是表面,核心差异在于:npm 在 JavaScript 世界里扮演的角色,远比 pip 在 Python 世界里要重得多。
三、六大维度,看懂两个生态的本质区别
1. 依赖安装:全局 vs 项目本地
执行 npm install,依赖直接装在当前项目的 node_modules 目录下。每个项目都有一份私有的副本,项目之间天然隔离。你不需要“激活”什么环境,进入项目目录就等于进入了正确的上下文。pip install 默认会把包装到全局环境。如果你不小心在全局装了一堆包,不同项目很快就会发生依赖冲突。所以 Python 强迫你必须先学会虚拟环境,才能安全工作。工程化的起点,凭空多了一道门槛。2. 虚拟环境:无感隔离 vs 显式激活
目录即边界。从项目 A 切到项目 B,cd 一下就好,依赖自动切换。“虚拟环境”这个概念被彻底隐藏掉了,因为它已经是默认行为。你需要手动 python -m venv venv,然后记得 source venv/bin/activate。如果忘激活,或者开错终端窗口,就会遭遇“明明装了包,却 ImportError”的经典翻车现场。这种手动管理环境的模式,至今仍是新手最大的痛点之一。3. 依赖解析与锁文件
早在 2017 年,npm@5 就内置了 package-lock.json,精准锁定整棵依赖树的版本。npm install 默认就是可重现构建。后来的 yarn、pnpm 虽然体验更好,但它们都尊重同一套 package.json 和锁文件理念。很长一段时间里,requirements.txt 只是一个顶层依赖的快照,连次级依赖都不锁定,“在我机器上能跑”成了口头禅。后来 pipenv、poetry 各自搞自己的锁文件,直到最近 uv 出现,Python 生态才算有了一个速度快、锁文件可靠的统一方案。但工具分裂的局面已经持续了十几年。4. 项目元数据与配置标准化
package.json 从第一天起就是项目的唯一描述文件:名称、版本、依赖、脚本、入口、配置……所有工具都认它,标准化程度极高。早期是 setup.py(本质是一个可执行脚本),后来出现 setup.cfg、MANIFEST.in,近年终于有了 pyproject.toml。但时至今日,你依然能看到同时存在多种配置文件的 Python 项目。这种历史包袱带来的碎片化,极大增加了上手成本。5. 脚本与任务运行
前面已经说过,npm run 就是内置的轻量级任务运行器。开发者统一入口,项目发现性极强。没有内置的统一任务接口。项目里可能同时看到 Makefile、tox.ini、noxfile.py、task.py 或者 README 里的一长串命令。工作流的起点不统一,新手往往要先读半天文档才知道怎么跑测试。6. 发布与分发体验
npm publish 一键发布。.npmignore 或 files 字段控制内容,简单到几乎没存在感。传统流程:python -m build 生成分发包,再用 twine upload 上传,还要管理 PyPI 凭证。虽然工具在改善,但相比 npm 的顺畅,还是显得繁琐。这无形中也影响了开发者“随手发个小包”的意愿。
四、不是“谁更好”,而是“两种生态,两种路径”
Python 的设计哲学是“自带电池”,标准库极其丰富,很多需求不需要第三方包就能解决。包管理器 pip 被设计成一个相对单纯的库安装器,而把项目构建、任务运行等工作交给其他专门工具。这在小型项目或单人开发时很灵活,但在大型协作和开源场景下,就容易显得松散。
JavaScript 从一开始就面对标准库匮乏的局面,社区不得不依赖 npm 来构建一切。这种倒逼机制反而让 npm 发展成一个深度集成到语言运行时、统一了依赖、构建、任务、发布等所有环节的“操作系统级”包管理器。
所以回到开头的问题:为什么那么多开源项目选 npm?因为对 JavaScript 生态来说,npm 不只是图书馆,它还是水电管网,甚至是城市的设计蓝图。你一旦身处其中,就自然而然地用上了它。
五、一些值得关注的改变
好在 Python 生态也在快速追赶。由 Astral 团队开发的 uv,正在把依赖安装、虚拟环境管理、锁文件等体验提升到接近 npm/cargo 的水准。未来 Python 的工程化体验一定会越来越丝滑。
但有一点是确定的:Node.js 那种“装好一个运行时就拥有一切,打开项目就能跑”的一体性,依然是今天工程化体验的标杆。