Python 包管理这七年是怎么演变的?搞懂这些,依赖冲突不再头疼.
七年前我刚开始写Python的时候,装包靠的是pip。那时候pip还很原始,就像一个老实巴交的搬运工,你让它装什么它就装什么。你告诉它装A,它就去装A。你告诉它装B,它就去装B。它不管A和B之间会不会打架。
然后我就踩坑了。项目需要Flask和Django,pip把两个都装上了。跑代码的时候直接报错。我一查,两个框架依赖的Werkzeug版本不一样。Flask要2.0,Django要1.x。pip没能力协调这个冲突。我当时的解决办法是删掉一个框架。
这个阶段叫手动管理依赖期。你得自己记住哪个包需要什么版本,装错了就删掉重装。那时候好多教程都在教“用虚拟环境”。virtualenv成了救命稻草。每个项目建一个独立的Python环境,包冲突的问题就被隔离了。
后来pip有了一个改进,引入了需求文件。你把依赖写进requirements.txt,pip安装时会读这个文件。但问题还在。需求文件只是把包名和版本号列出来。它不会自动帮你解决复杂的依赖树冲突。
2016年Pipenv出现了。这个工具尝试把包管理和虚拟环境合并。用Pipenv的人不用再手动激活virtualenv,一个命令搞定。它还会生成Pipfile.lock,锁住所有依赖的具体版本。当时很多人欢呼Python终于有了类似npm的体验。但Pipenv的速度是个硬伤,解析依赖慢得要命。你改一个包版本,它可能要花好几分钟重新计算所有依赖。
2018年Conda开始流行。Conda不是专门为Python设计的,但它管理Python包的体验很好。它最大的优点是能装非Python的依赖,比如C语言库。做科学计算的人特别喜欢它。Conda用了一个叫做SAT求解器的算法来解依赖冲突,效率比Pipenv高不少。
真正的转折点是Poetry的出现。Poetry用了一个新的依赖解析引擎,速度比Pipenv快很多。它把项目配置、包管理、虚拟环境全部塞到一个工具里。你只需要一个pyproject.toml文件,所有信息都在里面。Poetry还严格遵循PEP 518规范,这在社区里赢得好感。很多人从Pipenv转投Poetry,就是因为它“快”而且“干净”。
但Poetry也有它的毛病。它的依赖解析有时候会过于严格。你装一个简单的库,它可能把整个依赖链检查好几遍。遇到复杂的依赖场景,它也会卡住。
最近两三年,pip本身在进化。pip 20.3开始有了依赖解析器,能自动检测并提示冲突了。你再也不用盯着报错信息自己推理。pip检查发现A要版本1.0,B要版本2.0,它会告诉你这两个版本不兼容,让你选一个。这虽然还是没完全解决问题,但至少让你知道问题出在哪。
如果你还在为依赖冲突头疼,试试这么做。别把所有包都装到全局环境里。每个项目用虚拟环境,这是最基本的一步。锁定依赖版本,不要写大于号的宽松版本。写成“Flask==2.3.0”而不是“Flask>=2.0”。加入依赖检查工具,比如pip-audit可以扫描你项目里的安全漏洞和兼容性问题。
回看这七年,Python包管理从原始的pip走到了有解析器、有锁文件、有统一规范的时代。每次变化都是因为有人被依赖冲突折磨够了。工具在变好,但核心逻辑没变。搞清楚你项目里每一条依赖的来源,别让它偷偷选版本。做到这点,大部分冲突都能避开。