Python的pip依赖把我折腾惨了,原来requirements.txt和poetry能打出火星撞地球

讲个真事儿。

上个月我接手了一个维护项目,代码仓库里躺着两个文件:requirements.txtpyproject.toml。两个文件都在,而且都在用。我一开始没觉得有什么问题,反正都是管依赖的嘛,各管各的呗。

结果我运行poetry install装完依赖,再跑测试------全红了。我又用pip install -r requirements.txt重装了一遍,测试又全绿了。更诡异的是,我把两个文件里的依赖版本逐一比对,发现它们写的版本号一模一样。

一模一样的版本号,装出来的环境却不一样。

那天下午我花了四个小时才搞明白一件事:requirements.txt和Poetry,看起来都在管依赖,但它们对"依赖"这两个字的理解完全不同。 当两个工具同时出现在一个项目里的时候,打的不是架,是"火星撞地球"。

先搞清楚 requirements.txt 到底是什么

requirements.txt本质上就是一个文本文件,一行一个包名。你可以写成这样:

复制代码
requests
flask
numpy

也可以写成这样:

ini 复制代码
requests==2.31.0
flask>=2.0.0
numpy~=1.24.0

它没有什么复杂的结构,就是一个清单------你要什么包,我就记什么包。

但问题来了:requirements.txt只记录你直接依赖的包,不记录这些包自己又依赖了什么

你装requests的时候,requests会顺带装上urllib3certificharset-normalizer等好几个包。这些"间接依赖"在requirements.txt里是没有的。除非你用pip freeze把它们全部导出来。

pip freeze导出的是当前环境里所有已安装的包------包括直接依赖和所有间接依赖。这看起来解决了问题,但带来了新麻烦:你分不清哪些是你主动要装的,哪些是别人捎带脚装上的。而且如果不同的人在不同的时间执行pip freeze,由于PyPI上的包版本在变,生成的requirements.txt可能完全不同。

更麻烦的是,pip没有内置的依赖冲突解析器 。假设包A需要urllib3的1.x版本,包B需要2.x版本。pip不会提前告诉你"这两个不兼容",它会直接安装------装成什么样取决于安装顺序。可能这次跑得好好的,下次就因为版本变了莫名其妙地挂了。

这就是为什么很多Python项目会出现"在我机器上能跑"的经典问题。

再说 Poetry 是怎么做的

Poetry的核心理念是:依赖管理不应该是个事后补丁

它用两个文件来管依赖:

pyproject.toml ------ 你告诉Poetry你想要什么。比如:

ini 复制代码
[tool.poetry.dependencies]
python = "^3.9"
requests = "^2.31.0"
flask = ">=2.0.0"

这里的^2.31.0意思是"兼容2.31.0的版本",可以自动升级到2.31.x但不能升到3.0.0。你写的都是"版本约束",不是"精确版本"。

poetry.lock ------ Poetry帮你算出来的精确版本。它记录了每一个包------包括所有间接依赖------的精确版本号、哈希值等。这个文件是自动生成的,你不应该手动编辑它。

关键区别在于:Poetry会在安装之前先做依赖解析。它把整个依赖树都算一遍,确认所有版本都能兼容,然后才动手安装。如果发现冲突,它会直接告诉你"这几个包不兼容",而不是先把东西装上再说。

而且Poetry会自动为每个项目创建独立的虚拟环境,你不用再手动python -m venvsource activate

火星撞地球是怎么发生的

回到我遇到的那个项目。

requirements.txt是手动维护的,里面只列了五六个人直接依赖的包。而pyproject.toml是Poetry生成的,它记录了完整的依赖树。

表面上看两个文件里的版本号一样,但实际上:

requirements.txt里写的是requests==2.31.0,但没写它依赖的urllib3是什么版本。而poetry.lock里锁定了urllib3==1.26.18

问题出在另一个间接依赖上 。项目里还用了一个叫botocore的包,它要求urllib3的版本小于2.0。pip install -r requirements.txt的时候,pip先装了requests==2.31.0,它默认装的是urllib3的最新版------当时已经是2.x了。然后装botocore的时候,发现urllib3版本不对,pip的处理方式是悄悄把urllib3降级到1.x 。装是装上了,但requestsurllib3的搭配变了。

而Poetry不一样。它在安装之前就把整棵树算了一遍,发现requests==2.31.0botocoreurllib3的版本要求有冲突,直接报错终止,让你去解决。

一个工具悄悄帮你"解决"了问题(但其实没解决),另一个工具拒绝帮你"解决"问题(但保证了环境的一致性)。

这就是两个工具放在一起用的后果------你以为它们在做同一件事,实际上它们的哲学完全相反。

那到底该用哪个?

如果你只有一个简单的脚本,或者是一个一次性任务,用piprequirements.txt完全够用。轻量、直接、不需要学新东西。

但如果你的项目有以下任何特征,建议认真考虑Poetry:

  • 有多个开发者协作 ------ 每个人装出来的环境必须一致
  • 有CI/CD流程 ------ 构建环境必须可复现
  • 依赖超过10个包 ------ 间接依赖的冲突风险急剧上升
  • 要打包发布 ------ Poetry内置了构建和发布功能
特性 requirements.txt Poetry
配置文件 requirements.txt pyproject.toml + poetry.lock
记录内容 直接依赖(或全部已装包) 直接依赖 + 完整依赖树
依赖解析 安装时,无冲突检测 安装前,自动解决冲突
环境隔离 手动配合venv 自动创建和管理
开发/生产分离 不支持 支持分组
学习成本 中等

几个常见误区

误区一:"我用pip freeze导出了所有包,就跟Poetry的lock文件一样了。"

不一样。pip freeze导出的只是当前环境的一个快照,它没有记录"这些包之间的依赖关系是什么"。Poetry的poetry.lock记录的是完整的依赖树结构,而且带有哈希校验,能防止安装过程中被篡改。

误区二:"我把requirements.txt和pyproject.toml都留着,两边都兼容。"

这正是我踩的坑。两个工具同时存在,迟早会出问题。选一个,删掉另一个。

误区三:"Poetry太慢了,不如pip快。"

Poetry在依赖解析阶段确实比pip慢,因为它要做完整的依赖树计算。但这个"慢"换来的是"准"------它确保装出来的环境是对的。而且这个解析只在第一次安装或更新依赖时做一次,日常开发中影响不大。

如果你非要用requirements.txt

有些团队暂时无法切换到Poetry,或者项目已经用requirements.txt跑了很多年。这种情况下,至少做到这几点:

  1. 永远锁定精确版本 ------ 用==而不是>=~=
  2. 区分两个文件 ------ requirements.in记录直接依赖(人工维护),requirements.txt记录完整锁定(自动生成)
  3. 在干净环境中重新生成 ------ 每次更新依赖时,在新虚拟环境里执行pip freeze
  4. 考虑pip-tools ------ 它是一个轻量级方案,能帮你自动生成带完整依赖树的requirements.txt

一句话总结

requirements.txt是一个"购物清单"------你想买什么写什么;Poetry是一个"采购系统"------你说你想要什么,它帮你算好买什么、怎么搭配、会不会打架,然后一次性搞定。

两个工具没有绝对的好坏,但它们服务的是不同阶段的项目。小脚本用requirements.txt,正经项目用Poetry。别两个一起用,别让它们在一个项目里打起来。

相关推荐
狗哥哥1 小时前
用“十步学习法”带你学会事件驱动架构
后端
挽安6211 小时前
Spring Boot自动配置原理:从@EnableAutoConfiguration源码一步步看懂
后端
深入云栈2 小时前
Netty 4.2.x 源码深度解析 (十二):NIO传输——NioSocketChannel与NioServerSocketChannel的IO读写实现
后端
王的宝库2 小时前
GO常用标准库包
开发语言·后端·golang
php@king2 小时前
hyperf初步认识和安装
后端
LEE2 小时前
别再堆 AGENTS.md 了:前端团队如何把 AI Coding 做成一套可执行的工程系统
前端·后端
geovindu2 小时前
python: Face Recognition
开发语言·后端·python·人脸识别
工具派2 小时前
markdown在线编辑器怎么选?渲染管线的3个坑和md转PDF跑版记录
前端·后端
黑马程序员毕设3 小时前
基于Java的仪器管理系统设计与实现
java·开发语言·spring boot·后端·微信小程序