当一个Python项目只有寥寥数个包时,装依赖是件很省心的事。建好virtualenv,敲几行pip install就可以了:requests、fastapi、pytest,统统不在话下。
可一旦项目成长起来,忽然某天你发现,repository里躺着几十乃至上百个package。其中一部分跑在线上,一部分服务于测试,另一些则属于lint、文档、开发工具,或者仅在某一操作系统下用到。这时脑海里冒出的疑问越发密集:同一套代码,本地正常而CI报错,理由是什么?升个包,为什么把旁边另一个包弄坏了?重装一遍,拿到的环境为何不是原先的样子?为什么requirements文件莫名分身出好几个?添加一个依赖,又为什么引发十余个包连着变动?
显然,Python依赖管理已不再是装包这么简单,它已然成为一项项目管理课题。
依赖图膨胀的速度,远超requirements文件
打个比方,某个项目直接引用FastAPI与SQLAlchemy这两个顶层包。看似清爽,但这两个包各自还有下级依赖。真实环境会更接近:FastAPI下面挂着Starlette和依赖annotated-types的Pydantic,SQLAlchemy一侧连同greenlet,外加httpx依赖httpcore等等。
亲手写进安装列表的,仅处在依赖图最上层。项目扩张时,这棵树也跟着长出更密的枝叶。
雪上加霜的是,包与包之间会对版本提出排他要求。比如package-a声明需要urllib3>=2.0,而package-b则要求urllib3<2.0。此刻安装两个包可不只是下载压缩包,解析器要去反复试算:究竟有没有哪一个urllib3版本,同时让所有约束都心服口服。稍有不慎,依赖冲突就此酿成。
requirements.txt能用,但扛不住项目扩大的压力
对为数众多的Python工程而言,requirements.txt仍然是稳妥的起手式,一两个简单项目只需一行记一条即可。然而当规模爬上一定台阶,文件就会呈蔓延之势,requirements-dev.txt、requirements-test.txt、requirements-docs.txt、requirements-prod.txt接连问世,有人转而使用目录组合:requirements/base.txt、requirements/dev.txt、requirements/test.txt、requirements/prod.txt。
问题的症结并不在requirements.txt本身能否胜任,而在于它已经很难单独描绘一个远为复杂的依赖模型。当这个模型被五花八门的文本文件拼凑出来,以下这些看似简单的问题便越发难以回答:跑应用到底必须依赖哪些包?哪些是开发用的?真正跑过测试的是哪几个版本号?四处弥散的文本文件里,谁才是真正的source of truth?CI该按哪个装?生产该锁哪一版?项目越庞大,上述界限就越不容含糊。
回归统一下潜:pyproject.toml站稳标准化的位置
近来,现代Python项目逐渐转向以pyproject.toml作为项目描述档案,用它登录项目元数据与依赖项,而不是把它们分派到多个不同的文件。围绕my-project这个名称、requires-python约束,以及包含sqlalchemy种种入口的dependencies内容,都可以在这里一次性阐明。终于不必再把关键的依赖声明零散铺到若干不同文件中。
依据Python Packaging User Guide所述,[project] table正是项目元数据的集装箱。dependencies与optional-dependencies等信息统统在这里获得官方定义的容身之处。这也解释了为什么新开的Python项目通常应当把pyproject.toml尊为标准清单,而非围着老旧的setup.py基座再三调整绕行。setup.py没有被一脚踢开,但项目的元数据与构建配置,已经是pyproject.toml无可争议的现代福地。
运行依赖不是你放单元测试的大筐
把像SQLAlchemy的同类项和开发期临时用的那些统统倒进运行依赖——这显然不够精致。
当前打包机制有一条分类轨叫做Dependency Groups,可在[dependency-groups]的段落中娓娓写明,帮助版本分出产线旁的人手。它为pytest那样只在开发环节站岗的工具箱,给了独立的署名册,从此它们不必被迫登记成使用时正在搭载的必备组件。这一机制是由规范PEP 735专门引入的,面向开发依赖及其他不作为distribution依赖暴露的分组。该项PEP在2024年成为最终通过的标准文件。它和列在optional-dependencies下的成员带着不同牌号:dependency groups只在内部开发线下运作,并不随打好的轮子一并进入发布元数据目录。只要项目开始膨胀,这条划界会对你的日常判断不断有所帮助。
各小组一旦出现大项目里,dev、test、docs、linting、type-check这些目标差异越来越大,如果用同一环境的单体做打包,装机会被拖得迟缓,依赖求解也更让人晕头。而这正是第二类新增角色可派上用场的地方——通过dependency groups把路径拟出来,需要工作的模型直接发一个组就是。这里的任务也可被拆分成互相隔离的CI关卡:lint步骤在运行如pytest的工作上并无必要去劳师动众补上全库;只跑文档的pipeline不必为了拉起数据库的引擎事先揪过来一套驱动兵器。这一观念表达了一种将分散需求正经落成条目的实践,在此不再只指望没有被显式记下的整体环境的安装依赖。
函数后面的那张叠加层:optional依赖的生存逻辑
在开发端的依存定义之外,项目内还有一种东西叫“用户选择用的功能分量”——在自命名的库上下文中其实可以自己挑选。
换成举例的话,现在我们缔造一款Python图书馆包,试着插一面能连通PostgreSQL的旗帜,成为让世界上想在某情势来安合整的赞助商选项?而不是非要拉近lib中的所有包裹强推整组一起来。
[project.optional-dependencies]这张信封里,文档中就用来放可另类的补丁类特征。客户在有心投喂这种集成装备那一刻才抽出等待的配件开关来咬它。那些叫做pytest组的跟天然形副的兄弟并无完全的可拼路径——前者当打产品及给予所有过来看的好客人时通常会留下写入上游元数据留下徽记;后者一辈子只在自己的器材库里浮沉不会在给予全世界邮出包裹公开印章登记。给这些分类里拆出来的“能力”做标签、有条可寻,则你就自然觉得每拾弃一丛都能向群解释它是聊哪个世界段的旁写。
上面显式的表示不代表就不用理那些凡持需求约束能跟上时效。像声明写作fastapi>=0.100,库对你的诚实就是不要特别提前下降版数。而现在你手边是fastapi 0.115配httpx 0.27;待六个月再有人拿配置文件重搭,可能解包仪式揭出来的是fastapi 0.120与0.28版配,双方都在那个下界的范围内活着就是没错,但是谁也保不住它是否是原初尝过的样子。也就是说,直到有锁定件——lock文件摆出来镇守,指定明确解得的具体结果记录那一条(应写清该版本是从单一源头追踪到副本坐标甚至相关文件之标示性总结)。这也将两个诉求对成一条开口,“让人明白这个项目它可能装进范围内的各类版本分别是哪几种可供其选择”;另一个是不可退让的实现:跑环境当中应该锁到作为真正供给了原版系统那群原子排上集合的命令行召唤。
锁定和把所需统统捏成固定标记却并非一体两面可互换的对立面。若每次都去做这个覆盖性的方案就是写死成绝对硬格式,那这就变成以丧失大部分可触碰利益争取某种呆板持久安全,每当要拉拽动一个坐标都须人工全动一遍来捍卫一些低限正确。锁定文件自动切边,给的可不只是表面的冻结——它作用在第二次定位出所跑版本包集合的作用侧;而不必让你pyproject.toml上顶端的手写符号也跟着并过来写成百害而无新增灵活提升性质的那种坚轮。越喜欢组装大型系统和面向一切打算的人群去播种长期仓库型的发布方,必然对二重面向意义和利用原理越是频繁往返点头。
锁定而不僵化:从一次解析迁移到工作流思维
工程项目有了体量之后,团队所要的就不再仅是一个pip的基础更新信号。大家指望的是一套稳定的执行链。一条是负责纳入更新(往往是改过pyproject后再生成它相应的锁定点)。一条是让集成过程依据那份不曾乱跑的裁定捆绑出的原版本发起。再一条是确保交付投放端的依赖格局如计划配合运转。目前这一生态里的uv正把这些问题视作事务层来处理可提供范本之模样。
比如我需要httpx便在已有项目环境中uv add httpx,此时它一手写入在依从规定下的制品,另只随手地变化解析记录指针填充出新安置物件信息进日志,两处实时并举不乱串。与相对命令是uv remove XXX以及提供相差不多的是uv run pytest——就是你想在设定的运行池上起那样执行的移动指令。官方一篇有解释说ug这角色提供项目依赖维护、锁定文件读写和跟踪、环境相同性适配给数棵树相关统计以及分配多个互关组团(workspace)容纳的操作。这些都是手工去改若干文件并将同步、去重统一接二连三地做排摸对比的那种往日作风来不了的。
Monorepo因素把每进一步边界的提问放大更大一圈
接下来轮到的是一个仓库装入多个可分解组成项目群,例如看一个根底下竖出了services路径再开api,然后在手下再并进worker、scheduler这三个箱子;同座也紧挨libraries以及各自标示的database和 messaging的子目录,旁一个 tools 下还卧一份 migration的替换管桩,凑成一个完整围拢集合。既然里面的成分可能有清单大几十项的各式打包单元还有各接的任务作业运行在同期,就出来这么一道题:什么是受全部子组织共通追求协同的基底并列库?余下的归于特定个别服务或是既护又不供外界配装项目牵住的一块专属连接?此时若想布局规束这些多个并联工程的抽象脉络成为一种利器被需出唤到的显式层级——“workspace统一归秩实现的管理面板性能”。现行设施里uv是支持对这类相关联、出现在一起的若干程式包交给同一个平面的控制承接处理。根本图的方式可助项目中不断续生增加一个两个装配,也在把它们分组安排在一个表达权确定的明确路径中运转之用处:是为系统创建项目端可知性的谱系安排关系做出良好清晰引导的解决方案。
危机之核也许是内控的杂乱无章之患
有不少人一提起这套环境的协调便叹气喊痛,以为就是Python语言本身的缺陷给埋下痛果——是把因果关系放错了地方。随着代码体系沿着台阶越来越高,我们要做的事和操作对象逐渐庞庞越杂,自然而然生出这个大肚子而已:早期的件组几乎是只手可数,现在处在过渡档次里面有五十六件式若干加减的都算平常。假以一个重型工程就完全不是各来源均拟记录加减的那种表现,因为它缠绕出嵌套多层次体系,里面不光是隔着工序各切片,也可以另外分出用着的运行时语言的年代班次差,还闯得进一砖自有造模原铸边构元基因积层组装的能力对象单位并入框架内容根中整体交会的扩态系统中进得去那广大编制形态中。
该阶段的真实解决要朝向以下几点方向性的引领推动:务必给出各侧看得明白的宣布目录册,有可复订的一套完整打包体以实现按设定的流程回查解析顺序组合的那一包固化成效能被返演出现,并在环境栅栏中足够隔好井深且又能使它自动稳接地向前作力控配平的同步机制为安全流通开顺畅通行道路。
看清楚一份合格规划下具备的三合一听你受命的项目描述景观新阶层状态场景展示架构陈列台
取出来想象如排品分配得很有规律的一条规划中有:名为my-project对接口置文件中摊开出根区展示一篇供理解:锁定代表版本结实地存有叫起名叫uv.lock(说明此图为据),接着名字标my_project文件担任源结构容器处于src之域;副视框里安了一项持续保障测试领间的tests包和携带对外备注册的README文稿作为手册留遗凡当旁读者周览之端具备导览洞境时用的门户指引纸张由此置于门前墙上的固定阅读处公示给大家。
我们再试图阅读其上抽象出的解耦合对位线模型可以暂分成以下几个层带规划:一列算真实活动所需制分类进入runtime的;再一类位是打浮的可按需要部署交交的 optional特性开关等;最末第三层区域还有可供给多样开发功课使用专门修列小组装配那组的 development套餐集合的纵行条目层级编入的。即测试的,检验格式完备的,连同分门析疑的解出实体集在内。
这个规格式放置了原来说不清的划以分开类属;有一档叙清楚总体纲要只是要达到什么、可采纳何等高程上怎么需求其存在规格上定序;另一由多元组合能够讲好哪一支任务要对准的是次项目内小范围工作台以任务命名做锚点再分配针对性的补件内容如何去找出他们需要什么来助配各自分配的点工作照进行程操作的聚焦构成套餐类的;此外还有一篇全局型的结算图表单位可以告诉你这条源图解出了严丝布局的那一次落地会落地出来各种确准确确的结果坐标(用锁版本集)。最外面时时期的调配管家性质的一种装置便起着随着依赖拓扑编制外流风险自动跟随保持两者一致的净差别协调动态同步机制。
而后要是站到一个较开阔视角回想便会觉察软件依赖对全局设计意义上的重要性。不少人习惯以为该事情近似补遗是晚于功能完本,之后再过去随便抓补的那个待填的口部擦屁股事宜;对于小小的一次全执行时这样做最多无关风寒小事,小损害更易于回避;但在由多个服务彼此相关的浩然大迷宫,这是极其靠不稳妥的选择更并埋较大长久随机爆发断裂崩塌类险。”一处升降自牵全身从层层梯下叠基座一直徘徊变幻出来像不散的漂游影像阴。是否还能把这类变更影响束成为某种已确定及计划既成不变的形式收回到人眼光所及掌能控制的那一环——使变更有预测性回归为主干——这在程序设计级别的类群分离架间术中将上升为策略结点逐渐凝聚为语言政策有义务寻求更靠结实合适的解决法门的一点。
也许不绕圈的意义就只是在朴素的口诀箴我一种行事手谕:简洁指出你正走上第一个阶梯是把零散封壳码打包投置到环宇的那木栓式单一动作;“上进一步趋势,通过上述那些抽象构件的多层规定体式引入如约束规集控制块并借此对复杂协同宇宙的拓扑有能予以演算定义明确预见锁略达到实体基础装配水平划界的长期确定局势维护效;下出最终阶梯把以上组建既存的设施状态固冻变成可以任意拿复制重播操作的单位工序记录生成标称等控产品出来的结晶装置放到记忆厂库存长久如岩石”。
小结:Python打包生态正沿着标准化元数据、PEP 735依赖组、以及可复现锁定的方向演进,来支撑越来越大规模的工程;每一次组合因目标情形被分门定义,层层消解错综让全景有维护掌握并持续推进延伸意义可保障。其目的从来不是彻底剪卷复杂度而是让它在光下变成看着明白、能备份还原同时被好控制和面对变大带来的总爆发可靠达成关系守则的整体模型展示在生活其中处事日常。”
