一个63KB的库,实际占用620KB。这不是bug,是Python生态的集体盲区。
开发者Johan Lübcke花了多年时间优化代码性能——20倍、100倍的提速他都做到过。但他最近发现,自己忽略了一个更基础的问题:下载体积。没人讨论它,但边缘部署和Serverless冷启动都在为此买单。
「轻量」是个谎言:依赖树才是真相
几个月前,Lübcke对比了三款HTTP库。表面数据很干净:requests 63.4KB,httpx 71.8KB,aiohttp 1.7MB。但加上依赖后,故事完全变了——requests膨胀到620KB,httpx 560KB,aiohttp 2.6MB。
「包本身很小,但总体积完全是另一回事。」
这暴露了一个行业惯性:开发者选库时看功能、看性能、看社区活跃度,唯独不看「递归下载体积」。更麻烦的是,现有工具要么只显示已安装包的大小(pip list),要么需要实际安装才能测量(pip download)。没有工具能在安装前给出完整的依赖树体积。
Lübcke找了一圈,发现最接近的是pipgrip——但它只解析依赖关系,不计算体积。其他方案要么依赖本地pip子进程,要么需要真实下载文件。
「我想在安装前就知道大小。我想看到全貌——包本身加上每一个传递依赖。」
零下载方案:纯API架构的取舍
他最终写了一个叫pip-size的工具。核心设计很克制:不调用pip子进程,不实际下载任何文件,纯靠PyPI的JSON API获取元数据和文件尺寸。
执行逻辑分三步:解析用户输入的包名和版本约束→递归查询PyPI API构建完整依赖树→累加所有wheel文件的体积。输出格式模仿了pip的依赖树,但每一行都带文件大小。
以requests为例,工具会展开这样的结构:
requests==2.33.1 63.4 KB (total: 620.4 KB)
├── idna==3.11 69.3 KB
├── certifi==2026.2.25 150.1 KB
├── charset_normalizer==3.4.7 209.0 KB
└── urllib3==2.6.3 128.5 KB
这个递归过程处理了边缘情况:版本冲突时按pip的解析策略选择最新兼容版本;遇到平台特定wheel(如manylinux、win32)时,默认按当前平台筛选,但提供--all-platforms开关查看全部变体。
纯API方案的优势是速度。实测解析requests的完整依赖树耗时不到2秒,而pip download需要实际下载620KB数据,在慢速网络下差距更大。代价是精度:PyPI API返回的是wheel文件的理论大小,实际下载后解压到site-packages的体积会略大(通常10-20%,因为wheel是zip压缩格式)。
Lübcke在文档里诚实标注了这个限制:「测量的是下载带宽占用,而非磁盘最终占用。」
为什么体积突然变得重要
高性能库开发者关注体积,Lübcke总结了三个场景。
边缘设备部署。树莓派、工业网关、物联网节点的存储以MB计。一个宣称「轻量」但拖入500MB依赖的库,本质是负债。
Serverless冷启动。AWS Lambda、Google Cloud Functions的启动时间与包大小正相关。更小的包意味着更快的冷启动,直接换算成更低的账单和更好的用户体验。
CI管道效率。每次构建都重新安装依赖时,体积差异被网络延迟放大。一个团队每天触发几百次构建,累积效应可观。
这些场景的共同点是:开发者开始用基础设施的视角审视依赖,而不再只关心功能是否work。
pip-size的GitHub仓库在发布后一周内获得400+ stars。一条高赞评论来自一个边缘AI团队:「我们之前用requests,发现镜像体积多了600KB后换成了httpx。现在用你们的工具做预检,省了很多回滚。」
另一个常见反馈是关于平台特定依赖的复杂性。某些库(如numpy、pillow)在不同平台拉取的wheel差异巨大,Linux x86_64可能只有几MB,而macOS arm64版本翻倍。pip-size的--all-platforms开关就是回应这类需求。
工具目前不支持conda生态,这是架构决定的——conda的元数据结构和分发机制与PyPI完全不同。Lübcke在issue里回复:「如果conda有等效的JSON API,扩展不难。但目前没有。」
体积优化会成为Python库的新竞争维度吗?requests维护者在相关讨论中回应:「我们在考虑拆分charset_normalizer为可选依赖,但会破坏向后兼容。」这印证了Lübcke的观察——历史包袱让「减肥」比「加速」更难。
你的生产环境镜像里,有多少空间是被「轻量」库的隐藏依赖吃掉的?
热门跟贴