Python依赖管理实战:从pip freeze到requirements.txt锁定环境

发布时间:2026/10/17 21:28:58
Python依赖管理实战:从pip freeze到requirements.txt锁定环境
简介面向Python开发者的一份简明教程重点讲解项目依赖包的获取与安装。教程清晰区分两种获取方式其一是使用pip freeze将当前环境所有已安装包及版本导出到requirements.txt适合备份整个环境的依赖快照其二是安装pipreqs工具后在项目根目录执行pipreqs ./依据项目中的import语句自动生成依赖清单适合只记录当前项目实际用到的库。随后还演示了在新环境中通过pip install -r requirements.txt批量安装依赖并说明了requirements.txt作为依赖清单在环境迁移和团队协作中的价值。资源为单个PDF文档大小约30KB篇幅虽短但覆盖从导出、分析到安装的完整流程适合作为初中级Python开发者在日常依赖管理、项目部署等场景下的速查参考。目前已有580人学习便于快速定位到自己需要的命令与操作要点也可作为团队协作时同步依赖配置的参考。1. 为什么「获取依赖包」和「安装依赖包」能卡住整个项目交付一个很反直觉的现状很多开发者写完代码、本地跑得飞起结果把项目交出去对方按 README 一装要么报错要么装了一堆根本用不到的包。根子往往不在代码而在「依赖包」这一层没做干净。pip freeze requirements.txt确实能导出一份清单但这清单里往往混着全局环境的历史残留和间接依赖拿它去安装等于把玄学带进了项目。这篇笔记要解决的就是两件事如何从项目里拿到一份干净、够用、可复现的依赖清单以及如何用这份清单在任何一台机器上把环境装到和开发时一致。适合正在做项目交付、需要维护多环境、或者刚被依赖问题折腾过的从业者。2. 用 pip freeze 和 pipreqs 导出依赖包两种思路与一份干净清单2.1 为什么不能无脑用 pip freeze全局环境是个黑匣子pip freeze的工作原理很简单它把当前 Python 环境里所有已安装的第三方包连同版本号全部列出来。问题就在这里——「当前环境里有的」不等于「项目需要的」。如果你在开发机上已经装过几十个包其中大部分是以前某个项目留下的、或者实验时随手装的那 freeze 出来的清单会把这些全部带上。更隐蔽的是间接依赖你项目里 import 了 A 包A 又依赖 B 包freeze 时 B 也会出现。这些包装上去不报错但是会拖慢安装速度、占用空间更重要的是别人拿到这份清单会完全搞不清项目真正依赖什么。另一个常见翻车点是环境隔离没做好。如果你没有用虚拟环境直接跑pip freeze导出的就是系统 Python 的全局包列表。换个机器装的时候那个机器上可能已经有不同版本的包于是出现「你这边能用、别人那边装上就崩」的情况。这不是代码问题是依赖清单本身脏了。所以先记住一个原则导出依赖之前先确认你用的是哪个 Python 环境。执行which pip或pip --version看路径是不是指向你项目所在的虚拟环境而不是系统目录。这一步能避免后面所有排查工作白做。2.2 pipreqs 按 import 反向扫描拿到最小可用清单如果项目已经写了一半没法从零记录依赖那就用 pipreqs 这样的工具。它的思路和 freeze 正好相反不看你装了什么而是扫描你源码里的 import 语句反推出项目真正用到的包。这样导出的清单是「最小可用集合」每一条都有代码里的引用依据交出去之后对方只需要装清单里的东西理论上就能跑起来。实际操作很简单pip install pipreqs cd your_project_dir pipreqs . --force --encodingutf8第一行是把 pipreqs 装进当前环境第二行进入项目根目录第三行让 pipreqs 扫描当前目录下所有 Python 文件。两个参数值得说明--force表示如果目录里已经有一份 requirements.txt直接覆盖它避免手动删旧文件--encodingutf8是处理源码里含中文注释时常见的坑不指定编码在某些平台上会因解码报错。执行完项目目录里会生成一个新的 requirements.txt里面的包名和版本号是按 import 语句反推的比如你代码里写了import requests它就会生成requests2.x.x这样的行。但 pipreqs 也有边界它只认 import 语句对于动态导入、在配置字符串里写的包名、以及通过入口脚本间接依赖的情况可能漏掉。所以它的定位是「快速生成一份能用的底稿」而不是最终交付物。我一般会在生成后跑一遍项目看缺什么再手动补。2.3 requirements.txt 里的版本约束写法直接决定安装成败不管是 freeze 还是 pipreqs生成的 requirements.txt 都是纯文本每行一个包格式是「包名版本号」或包名加比较符。这里千万别小看格式版本约束写不好后面安装时的冲突全是从这来的。requests2.31.0 flask2.3.0 numpy~1.26.0 celery5.3.4 # 这个包只在 Windows 下需要 pywin32; sys_platform win32逐行解释一下是精确锁定版本交付时最安全但会限制后续升级适合线上环境固定版本。表示允许不小于某个版本写起来宽松但装出来可能是几年后的新版本接口变化容易让代码崩。适合纯 Python 项目内部依赖不适合对外交付。~是兼容版本约束比如~1.26.0表示不小于 1.26.0但小于 1.27.0。它比可控比灵活。分号后面是环境标记sys_platform win32表示这行只在 Windows 平台才安装。同一个包在不同平台版本不同或者某些包只在特定系统下存在时用这个写法避免在 Linux 上报「找不到匹配版本」。这段格式本身也是后面安装环节的输入所以导出一份真实、干净的清单比学会安装命令更重要——装只是执行清单才是决策。3. 安装依赖包从 pip install 基础参数到 requirements.txt 一键安装3.1 安装单个包时常用的完整参数链很多人装包只会pip install requests等出问题才想起来加参数。实际上 pip install 的参数决定了失败的几率和排错的速度。先看一个比较完整的命令行然后逐项说明python -m pip install requests2.31.0 \ --no-cache-dir \ --timeout 30 \ --retries 3第一行python -m pip而不是直接pip是为了确保你用的 pip 和当前解释器绑在一起避免装了包但 import 不到的诡异问题。2.31.0锁定版本。--no-cache-dir表示不读本地缓存这个参数在解析出错时特别有用因为 pip 有时候会从缓存里取出一个损坏或下载不完整的包装完才发现行为不对。--timeout 30是每 30 秒没收到数据就判断超时--retries 3是失败后重试次数这两个参数专治网络不稳定的情况。如果包已经装过了但你想确认装到了哪个目录后面可以加-v看详细日志或者直接查pip show requests看 Location 字段。这里有个常见的误用直接执行pip install requests而不用python -m pip在某些机器上会触发「pip 安装在系统 Python、而项目用的是虚拟环境」的错位包装上了但项目里 import 不到第一反应往往以为是代码问题其实环境就错了。3.2 用 requirements.txt 一键安装顺序与冲突看哪里拿到一份 requirements.txt 之后安装是命令一条python -m pip install -r requirements.txt -i https://pypi.example.com/simple-r表示读取文件中的依赖列表并逐个安装文件里写的版本约束会一并生效。-i是手动指定镜像站地址这个参数在下载慢或访问不到默认源时是刚需把域名换成你所在网络环境下实际可用的镜像即可。执行过程中pip 会先解析整份文件的依赖树再统一下载、统一安装而不是逐行装。如果文件里有冲突——比如 A 包要求numpy2.0B 包要求numpy1.24两者在解析阶段就会报错告诉你哪些依赖无法同时满足。这时你要做的是顺着报错信息找到冲突的两行要么手工改约束要么把不需要的包从清单里拿掉。不要试图跳过检查常见的错误是有人看到冲突提示后直接--ignore-deps绕过结果是项目启动到一半才爆出「模块找不到」或「运行时版本不符」排查代价更大。3.3 虚拟环境安装依赖包之前先隔离安装依赖包这件事最好的实践是先隔离环境再动手。隔离工具现在首选 Python 自带的 venv不需要额外安装cd your_project_dir python -m venv .venv source .venv/bin/activate # macOS / Linux .venv\Scripts\activate # Windows PowerShell which python # 确认解释器路径已指向 .venv第一行创建名为.venv的虚拟环境第二、三行分别是不同系统的激活命令第四行验证当前 shell 用的 python 是不是虚拟环境里的。激活后再执行pip install -r requirements.txt所有包都只会装进.venv既不会污染系统 Python也不会和别的项目互相打架。有人觉得多一步麻烦就一直在全局环境里pip install -r requirements.txt。这个习惯在项目只有一个、机器也只服务这一个项目时勉强能用但只要机器上同时存在两个项目、各自依赖不同的 Django 版本全局环境立刻就会变成一团乱麻。真到那时候想回头就得手动一个一个卸比现在多花的时间多得多。所以我的习惯是只要开始一个新的项目目录第一件事永远是建 venv而不是写代码。4. 依赖安装避坑5 条让新手和熟手都翻车的真实问题4.1 装完依赖后 import 还是报 ModuleNotFoundError现象pip install -r requirements.txt执行成功没有任何报错但运行代码时提示某个模块找不到。原因最常见的是 pip 装到了一个环境而你的代码用的是另一个环境。具体来说可能是你激活了虚拟环境但 pip 命令之前被另一个路径的脚本覆盖了也可能你在 IDE 里运行代码但 IDE 的 Python 解释器没有指向当前虚拟环境导致 import 时去查系统路径。还有一种情况是包名和 import 名不一致比如 PyYAML 装完要import yamlPillow 装完要import PIL这类对应关系只有装了才知道。解决先执行python -c import sys; print(sys.executable)看运行时用的是哪一个解释器再pip show 包名看包的 Location 是否在这个解释器的 site-packages 里。两边对不上就重新激活虚拟环境或调整 IDE 解释器路径。对不上号时别急着重装重装十次也解决不了环境错位。4.2 requirements.txt 里有几十个包但项目实际只用五六个现象把依赖文件发出去对方装了半天结束时发现磁盘多了好几个 GB 的空间但项目代码里根本没用这些包。原因这份清单是从全局环境里pip freeze出来的把历史遗留包全部带了进来。pip freeze 只是机械地列出当前环境的所有条目没有能力判断「哪些属于当前项目」。解决改用 pipreqs 重新生成清单然后在虚拟环境里从零装一遍并运行项目发现问题再手动补包。如果项目已经跑在虚拟环境里也可以直接在虚拟环境里 freeze因为 venv 只会有你显式装过的包和它们的依赖比全局环境干净得多。多个项目共用一台开发机时这个坑尤其常见。4.3 安装时报错依赖冲突Failed to resolve现象pip install -r requirements.txt解析到一半报ERROR: Cannot install xxx because these package versions have conflicting dependencies。原因requirements.txt 里两个包对同一个间接依赖要求了不兼容的版本区间比如 A 要求urllib32B 要求urllib32pip 在解析阶段找不到交集就直接退出。这不是网络问题也不是包损坏是依赖树里存在真实矛盾。解决先看报错信息里列出了哪几个包在冲突然后查冲突包各自对那个依赖的约束范围。要么把其中一个包的版本约束放宽要么换一个不冲突的版本。如果项目里两个功能都强依赖需要升级其中一个包到新版本再改代码适配。遇到这种情况硬装是没用的--force-reinstall也只会在报错后反复走同一条死路。4.4 带 C 扩展的包装不上报错缺编译器或 wheel现象安装某个包时 pip 去下载源码包然后提示缺少编译器、库里没有某个头文件或者干脆卡在 building wheel 的过程里。原因这类包大多含 C/C 扩展例如部分数据计算库、图像处理库在没有预编译 wheel 的分发版本时pip 只能先下载源码再现场编译。Windows 上缺编译器是最常见的翻车点macOS 上则可能因为架构或系统版本不匹配。解决优先选择带预编译 wheel 的版本。方法是指定一个较新的稳定版或者用国内镜像站替换默认源镜像站会提供更多平台和 Python 版本对应的 wheel 文件。如果仓库本身只提供源码包那就得提前装好编译工具链按报错提示安装对应的依赖库。这个问题的根源不是 pip 本身而是分发方没有为你的平台构建产物只能在选择版本时多试几个。4.5 pip 下载慢、装到一半超时中断现象依赖文件不大但安装时进度条走到某个包就卡住最后报ReadTimeoutError或Connection reset。原因默认的包源在境外加上网络环境波动传输速度不稳定超过--timeout设置的阈值就被判失败。这在依赖包较多时几乎一定会出现。解决最直接的办法是执行安装命令时指定-i参数指向一个国内可稳定访问的 PyPI 镜像站。如果嫌每次敲参数麻烦可以写进配置文件让 pip 默认走镜像站配置文件路径按系统和用户目录不同装完后python -m pip config list可以确认当前生效配置。另外加上--timeout 60 --retries 5给网络波动留出余量。下载慢这种问题不需要玄学都是参数没给够。5. 锁定与复现让依赖包在另一台机器上装出完全一致的环境5.1 为什么有 requirements.txt 还不能保证可复现很多人觉得有了 requirements.txt 就能交付了这想法只对了一半。requirements.txt 里锁的是「第一层依赖」的版本但真正影响运行环境的是整棵依赖树包括每个包自己的依赖。比如你锁了requests2.31.0而 requests 自己依赖的 urllib3 和 certifi 没有被锁住那么在不同时间安装时pip 会拉取「当时最新的满足条件」的间接依赖版本两端环境在细节上就已经不同了。还有一个变量是 pip 本身的解析策略。同一份 requirements.txt老版本 pip 和新版本 pip 解析出来的依赖子集可能不同——新版本会补充一些旧的没写的依赖。这就导致你在开发机上测试通过的组合交付对方后安装出来的子树是另一个样。所以要让依赖安装真正「可复现」只锁清单上的包名不够要锁全。5.2 从零复现的完整流程新建环境、安装、再冻结常见做法是先建一个全新的虚拟环境只装这份清单验证项目能跑通再基于这个环境导出一份锁定全量依赖的文件。过程如下cd your_project_dir python -m venv .venv source .venv/bin/activate python -m pip install -r requirements.txt python -c import your_main_module # 验证能正常导入 python -m pip freeze requirements-lock.txt前三步是创建并激活干净环境第四步安装清单里的依赖第五步用一句 import 验证核心模块不报错最后把当前环境所有包连同间接依赖一起冻结到requirements-lock.txt。这份 lock 文件比原始 requirements.txt 长很多每一行都是精确版本号没有区间符号。之后交付时优先使用 lock 文件安装。日常开发如果要调整依赖改原始 requirements.txt再重新走一遍「新建环境→安装→验证→冻结」而不是直接在 lock 文件上改。这样原始清单保持可读性lock 文件保证可复现性职责分离。5.3 离线安装pip download 打包成一个 wheelhouse有些生产环境无法直接联网装包需要把依赖包先下载好再拿到目标机器上离线安装。这个场景下pip download是核心命令python -m pip download -r requirements-lock.txt \ -d ./wheelhouse \ -i https://pypi.example.com/simple-d ./wheelhouse表示下载到当前目录下的 wheelhouse 文件夹-i指定下载源。执行完后 wheelhouse 里是本机的平台对应版本的.whl文件把这些文件和 requirements-lock.txt 一起拷到目标机器。目标机器安装时用下面这个命令python -m pip install --no-index \ --find-links./wheelhouse \ -r requirements-lock.txt--no-index让 pip 完全不访问源站--find-links指定从本地 wheelhouse 目录找包-r读依赖清单。这个流程能做到完全离线、完全离线复现不依赖目标机网络环境。需要注意一点下载时在哪台机器上执行wheelhouse 里就是哪个平台的产物如果你在 Windows 上下载了 wheel 文件目标是 Linux 服务器大部分包直接装不上。多平台交付时要在各平台分别跑一次 download。6. 一个值得养成的习惯用「显式清单 锁定文件」双文件维护依赖前面绕了一圈落地到实际项目里我自己的依赖管理习惯是维护两个文件requirements.in和requirements.txt。requirements.in只写项目直接依赖的顶层包不写版本或只写区间它表达的是「这个项目需要什么」requirements.txt是通过工具从前者编译出来的锁定文件每行都有精确版本号和完整依赖树它表达的是「这个项目当前用什么版本在跑」。日常新增依赖时我在requirements.in里加一行然后重新编译生成新的锁定文件再把新环境跑一遍测试。这样做的直接收益是看requirements.in能快速理解项目依赖了哪些库交付或回滚时用requirements.txt能一次装出和开发一致的环境。没有这个习惯之前我常常遇到「三个月后自己回来看项目依赖了什么都说不清」的窘境——旧文档里写的是flask但实际跑的已经是另一个大版本而新同事按文档装完直接报错。这个流程在多人协作时尤其省事。对方拿到项目后只看requirements.in就知道该装什么用requirements.txt安装就一定能复现。它不需要引入额外重的工具链pip 自带的 freeze 加上一点约束纪律就能维持住。我现在的做法是每次发布前固定走一遍更新requirements.in→ 装进全新 venv → 跑测试 → freeze 到锁定文件 → 提交。这套动作不算复杂但能把你从一个「依赖问题反复折腾」的状态里彻底拉出来。希望帮到你。本文还有配套的精品资源点击获取