组件打包与发布:底层原理与 Python/JS 操作手册
罗光宣
本文说明如何把 Python 通用技术组件 和 JS 通用技术组件 打包、打版本、发布到本机,再在其它工程里安装使用,同时对涉及的底层原理进行系统详细的介绍。

零、打包的底层逻辑与要解决的问题
打包常见有两种目标:库/组件包(作为依赖被其他项目引用)与应用分发包(把整份应用打成可在某主机上直接执行或部署的形态)。本节主要讲前者——依赖图、解析、产物与安装——的原理与术语;后者在 0.14 做概念区分并与同目录《开发环境与支撑软件建设管理方案》衔接,不展开具体工具。 下面用图论与集合等基础概念把依赖与打包讲清,再落到行业术语与工具差异。先建立依赖图和树形安装 vs 扁平化的模型,并回答:不同分支引用同一包的不同版本,能否在安装/编译时正确区分?结论是:保留依赖树结构可以,扁平化会丢失“引用点”而不能。

0.1 基础用语(图论与打包术语)
以下用常见教科书说法定义,后文统一用这套语言。
图论用语
·有向图:由顶点(节点)和有向边组成的结构;边 (u,v) 表示从 u 指向 v。
·有向无环图(DAG):有向图中不存在从某顶点出发又回到该顶点的路径。依赖关系若不允许循环依赖,则天然是 DAG。
·树:每个顶点(除根外)恰有一条入边的有向图;有唯一根。依赖关系在“每包只被上一层某包引用”时可视为树;一般情况是 DAG。
·子树:以某顶点为根、包含其所有后继的子图。对应“某包及其传递依赖”。
打包与安装用语
·依赖图:以“当前项目”为根的有向图;顶点 = (包名, 版本) 的一个具体版本,边 (P, Q) 表示 P 声明依赖 Q。同一包名可对应多个顶点(不同版本)。
·解析:给定各节点上的版本范围约束,为每个节点选定一个具体版本,使所有边的约束满足;即求依赖图的一个具体实例。
·锁文件:把某次解析得到的“每个 (包名, 版本)”记录下来;下次安装按此装,不重新解析,以保证可复现。
·产物:构建得到的、可被安装器识别的文件(如 .whl、.tgz),内嵌或同捆元数据。
·wheel(.whl 文件):Python 的预构建分发格式;本质是一个按 Python 包目录结构组织的 zip 包,扩展名为 .whl。安装时无需在本地编译,直接解压到 site-packages 即可使用,比源码包(sdist)安装更快、依赖更少。
·元数据:描述“是谁、什么版本、依赖谁”等的信息;安装器只读元数据不读源码。
·registry:按“包名+版本”提供产物下载的服务(如 PyPI、npm registry);私有 registry 即自建同类服务。
·环境:一组“可被运行时找到的包”的集合 + 解释器/运行时版本;虚拟环境即与全局隔离的这样一组包。
·可编辑安装:不安装产物,而把源码目录挂到环境的查找路径,改源码即生效。
·语义化版本:版本号形如 主.次.修订;主版本不兼容、次版本向后兼容功能、修订向后兼容修复。
·可复现:同一输入在不同时间、不同机器上得到相同的依赖图与安装结果。

0.2 依赖图与“树形安装”:能否正确区分不同分支的不同版本?
依赖关系本质上是一张依赖图(通常为 DAG):顶点是 (包名, 版本),边表示“依赖”。同一包名可以在图中出现多次(不同版本),挂在不同节点下,即“一个分支引用 A@1,另一个分支引用 A@2”。
关键问题:在安装/编译时,能否让每个引用点只看到自己依赖的那一版? 答案:能,当且仅当安装布局保留“引用点”信息——即保留依赖图的结构,使“谁依赖谁”对应到文件系统上的路径关系。
·树形安装(保留图结构)每个节点对应一个目录;某节点的依赖放在该节点下的子目录中(例如 项目/nodemodules/A/nodemodules/C@1、项目/nodemodules/B/nodemodules/C@2)。这样,A 的代码在解析“C”时只会看到 C@1,B 的代码只会看到 C@2。不同分支引用不同版本,在安装时可以被正确区分,因为“引用点”由路径唯一确定。
·扁平化(丢失引用点)安装器把依赖“提升”到同一层(如都放到 项目/node_modules 下),同一包名在同一层只能出现一个版本。于是:要么只保留 C@1 或 C@2 之一,必有一方拿错版本;要么放弃扁平、部分保留嵌套,但树变深、重复多份。同时,顶层对项目根代码可见,项目若误用未声明的包(本应是某依赖的传递依赖),也能被解析到——即幽灵依赖。因此,扁平化既无法正确表达“同一包多版本”,又制造了“未声明却可用”的可见性。
下图为概念示意:左为依赖图(树),右为扁平化后“只能选一个 C”的后果。


结论:依赖图可以在安装/编译时正确处理“不同分支引用不同版本”,办法是按图布局(树形或等价地保留每个引用点对应的路径)。扁平化通过把节点合并到同一层失去了引用点与版本的对应关系,因此既会导致依赖地狱(版本二选一或树爆炸),也会导致幽灵依赖(未声明包对根可见)。
树形安装下,项目能否正确编译?若依赖图被完整记录且安装布局是树形(保留引用点),则可以:A 的编译/运行只会解析到 C@1,B 只会解析到 C@2,项目能正确编译(编译型语言)或正确运行(解释型语言)。前提是语言的模块解析规则是“从当前模块所在位置找依赖”(如 Node 从当前目录向上找 node_modules),这样每个引用点自然对应到其子树下的那一版。
不同语言、不同工具,是否都能做到?不是;取决于该生态是否允许“同一环境/同一进程里存在同一包名的多个版本”,以及解析是否按引用点查找。
语言/生态 | 同一环境中 A 用 C@1、B 用 C@2 | 说明 |
Node(嵌套 node_modules) | ✅ 支持 | require 从当前目录向上找 node_modules,A、B 各自子树下可挂不同版本 C。 |
Rust (Cargo) | ✅ 支持 | 依赖图可含多版本同一 crate,每个 crate 只链到自己声明的那一版。 |
Java (Maven/Gradle) | ✅ 可支持 | 依赖树可带多版本;类加载可按模块/类加载器隔离,不同模块可见不同版本。 |
Python (pip/venv) | ❌ 一般不支持 | 一个环境只有一个 site-packages,同一包名只能装一个版本;要同时用 C@1 和 C@2 需分环境(多 venv)或分进程。 |
C/C++ | ⚠️ 视情况 | 常见为一个构建一套 include/lib;多版本需不同符号/命名空间或分目标精心设计。 |
更复杂的情形:哪些能处理、哪些不能?
·能正常处理的:钻石、多分支引用不同版本(A→C@1、B→C@2),在树形 + 支持多版本的生态下可正确安装与编译/运行;约束可满足时,解析器求出一组具体版本即可。
·任何工具都处理不了的:约束不可满足(如 A 要 C>=2、B 要 C<2,没有版本同时满足);只能改依赖或放弃其一。ABI/二进制不兼容时,即便安装“对”了(如 C++ 或原生插件两版都装),同一进程若不能共存,运行时仍会出错。
·有的能、有的不能的:同一包多版本(见上表)。单例/全局唯一语义(如前端要求全应用只有一个 React 实例的 peer dependency):依赖图里可能出现两版 React,工具可能都装上,但运行语义错误;有的工具会做 peer 去重或告警,属“部分处理”。
因此:树形 + 完整依赖图是正确区分的必要条件;是否真的能正确编译/运行还取决于语言与包管理器的设计(单命名空间如 Python 则不行,按路径/模块隔离如 Node、Rust 则行)。

0.3 打包在做什么(一句话)
打包 = 把源码 + 元数据做成一个可被工具识别、安装、并在运行时可被找到的发布单元(即产物),使消费者能按名字和版本拿到一份确定、可复现的代码与依赖。

0.4 两条管线:构建与安装
·构建管线(作者侧)源码 + 包描述(名字、版本、依赖、入口等) → 构建工具 → 产物(.whl、.tgz 等)。产物 = 要安装的代码/资源 + 元数据。
·安装管线(使用侧)解析“要装谁、哪个版本/范围”(或读锁文件)→ 从 registry 或本地获取产物 → 解压/拷贝到约定目录(保持或破坏依赖图结构,取决于是否扁平化)→ 在运行环境中登记,使 import / require 能按该语言的解析规则找到包。

0.5 元数据:包描述里到底有什么(共性)
任何语言的包描述都在回答几类问题;字段名因语言而异,职责一致:
类别 | 要回答的问题 | 常见字段(不拘语言) |
身份 | 是谁、哪个版本? | name、version;有的还有 namespace/scope。 |
依赖 | 运行/构建依赖谁、什么版本范围? | dependencies、devDependencies、requires-python / engines 等。 |
入口与能力 | 别人怎么用、有没有命令行/插件? | main/module、entry points/bin、exports。 |
构建方式 | 用什么工具、怎么从源码生成产物? | build-system(Python)、scripts/构建约定(JS)。 |
描述与合规 | 这是什么、谁维护、什么许可? | description、license、author、readme。 |
原理:元数据是“包与安装器之间的契约”;安装器只靠元数据决定装谁、装到哪,运行时再按语言规则在约定目录里解析名字。

0.6 版本范围、直接/传递依赖、钻石问题
·版本范围:元数据里写的是“兼容的版本集合”(如 >=1.0,<2),不是单一版本。解析即在满足所有约束的前提下为每个包名选定具体版本;锁文件把该次解析结果固定下来。
·直接依赖:项目(或当前包)显式声明的边。传递依赖:通过有向边传递下去的后续节点。安装器递归处理整张依赖图。
·钻石问题:图中存在两条从根到同一“包名”不同版本的路径(如 A→C@1、B→C@2)。树形安装下可并存;扁平化下只能保留一份,必有一方版本不符,或退化为深树+重复安装(依赖地狱)。

0.7 构建与安装分离、可复现、分发与完整性
·构建与安装分离:构建在作者环境做一次,得到产物;安装在消费者环境只做解压、拷贝、登记。同一产物可多机多次安装;产物可缓存、可审计。
·可复现:① 产物不可变:同一 (name, version) 对应同一文件,可校验哈希。② 依赖图可固定:锁文件记录每边的具体版本,重装时按锁文件装。
·分发:产物通过 registry、私有仓库、本地路径或目录索引传递;包格式与分发方式解耦。
·完整性:用哈希(如 SHA256)或签名保证产物未被篡改;registry 或锁文件可记录哈希。

0.8 环境与隔离
·环境:一组“可被运行时找到的包”的集合 + 解释器/运行时版本。虚拟环境即与全局隔离的这样一组包。
·为何隔离:不同项目可能依赖同一包的不同版本;按环境安装使每个项目对应一张独立的依赖图,互不干扰。
·可编辑安装:将源码目录挂到环境的查找路径(或符号链接),改源码即生效,用于开发联调。

0.9 语言/运行时的差异(原理层面)
同一套逻辑在不同语言里表现为不同的模块解析规则,包格式和安装目录要迎合这些规则。
·Python
o查找:运行时按 sys.path 顺序找模块;通常把 site-packages 加入 sys.path。
osite-packages:Python 环境中存放已安装包的目录;一个包对应一个子目录,import 包名 即在该目录下查找。
o产物:wheel(.whl)= 按 Python 包布局的 zip,解压到 site-packages 即可用;sdist(.tar.gz)= 源码包,安装端需先构建再装。
·JavaScript/Node
o查找:require('x') 从当前文件所在目录向上找 node_modules/x,找到为止。因此不同目录可有不同 node_modules,天然支持同一包多版本并存(不同子树挂不同版本)。
onode_modules:Node 约定存放已安装包的目录;子目录名通常为包名,可嵌套(某包/node_modules/其依赖)。
o产物:tgz = 包目录 + package.json 的压缩;安装即解压到某 node_modules 下。
共性:都是“把包放到约定目录 + 运行时按约定规则解析名字”;差异来自解析顺序与是否允许多版本并存。

0.10 扁平化、幽灵依赖、依赖地狱(统一用图论说清)
·扁平化(hoisting)把依赖图中本应出现在不同子树的节点“提升”到同一层目录(如都放到 项目/node_modules)。即:把依赖图在文件系统上的投影从“树形/多层级”压成“一层或少数几层”,同一包名在该层只保留一份。代价是丢失“谁依赖谁”的路径信息(引用点与版本的对应关系被破坏)。
·幽灵依赖项目根未声明依赖某包 X,但 X 因扁平化被提升到根可见的那一层,根代码里 require('X') 也能解析到。即:未在依赖图中以根为起点的边声明的节点,因扁平化与解析规则而对根可见。一旦真正声明 X 的依赖被移走或升级,X 从该层消失,根代码会静默崩溃。
·依赖地狱依赖图中存在同一包名、多版本的节点(不同分支引用不同版本)。扁平化下同一层只能放一个版本,安装器要么只保留一个、必有一方版本错误;要么不提升、保留深层嵌套,导致树深、重复多份、磁盘与安装时间暴涨,且安装顺序或机器不同可能得到不同形状,难以复现。这种“要么版本冲突、要么树爆炸”的困境即依赖地狱。

0.11 工具差异(pip/poetry、npm/pnpm/yarn):原理上的取舍
·Python:pip vs poetry/pipenvpip:安装器,解析较简单(如先到先得),无官方锁文件。poetry/pipenv:引入锁文件,解析结果确定,可复现更好;poetry 还统一管理项目与虚拟环境。取舍:可复现 vs 与既有 pip/setup.py 的兼容。
·Node:npm vs pnpm vs yarnnpm:扁平化 nodemodules(见 0.10),安装快但易幽灵依赖与依赖地狱。yarn:引入 yarn.lock,确定性安装;yarn 2+ 的 PnP 不用 nodemodules,从缓存解析。pnpm:用内容寻址存储 + 符号链接,按依赖图挂载,不把未声明包提升到根可见层,故无幽灵依赖;同一包物理存一份,多项目共享。取舍:严格性、磁盘、速度、兼容性。
共性:都在权衡“依赖解析确定性、安装速度、磁盘、兼容性”;不同工具选不同折中。

0.12 打包要处理的核心问题(汇总)
问题 | 含义 | 打包/元数据如何参与 |
身份 | 谁、哪个版本;避免重名与混淆。 | 包描述里唯一 name + version(及可选 namespace/scope)。 |
依赖 | 需要哪些包、什么版本范围;避免缺库或版本不符。 | 声明 dependencies;安装器在依赖图上解析并安装。 |
放置与发现 | 装到哪、运行时如何被找到。 | 语言/工具约定安装目录与解析规则(如 site-packages、node_modules);包格式符合该约定。 |
可复现 | 同一输入得到相同依赖图与安装结果。 | 产物不可变 + 锁文件;构建与安装分离。 |
分发 | 消费者如何拿到产物。 | 标准格式产物;registry、本地路径、私有仓库等。 |
隔离与多版本 | 多项目/多环境不互相污染。 | 按环境安装;每环境对应一张依赖图,包不写死路径。 |

0.13 和“不打包”的对比、小结
·不打包:直接拷源码、改 path 或加链接。无版本边界、依赖不声明、难复现、多项目易冲突。
·打包后:有边界(名字+版本)、依赖显式、产物可缓存/审计、安装器在依赖图上统一处理;配合锁文件与环境隔离,可复现与协作可控。
小结:用图论说,打包与安装是在依赖图上做解析(为每边定版本)和布局(树形保留引用点 vs 扁平化丢失引用点)。树形安装可以正确区分不同分支的不同版本;扁平化不能,并带来幽灵依赖与依赖地狱。元数据、锁文件、registry、环境等术语均可在“图 + 约束 + 布局”这一套底层语言下统一理解。

0.14 两种打包目标:库/组件包 vs 应用分发包
前文讨论的“打包”均指库/组件包:产物(如 .whl、.tgz)被其他项目依赖、安装、引用,通过依赖图与解析在环境中共存。另一类常见需求是应用分发包:把整份应用打成可在某一主机上直接执行或部署的形态,供最终运行环境使用,而非被另一工程当作依赖安装。
目标 | 谁消费 | 典型产物 | 关注点 |
库/组件包 | 其他项目(开发者) | .whl、.tgz、.jar 等 | 身份、版本、依赖图、可被 import/require |
应用分发包 | 最终运行环境(主机/用户) | 可执行文件(exe)、安装包(msi/deb)、容器镜像、自包含目录等 | 运行时自包含、部署目标、发布与版本号 |
应用打包不解决“依赖图解析”,而解决“把应用 + 运行时(或镜像)打成可交付单元、在目标主机上开箱即跑”。开发时应用会依赖许多库包,发布时再对应用本身做应用打包;两种打包在流水线里先后出现。本指南以库/组件包为主;应用打包在本指南 第五节 有系统说明(入口与主程序、运行命令、环境、工作目录、多入口及 Python/JS 打成可执行/可部署的方式);具体形态与工具链(Windows exe、微信小程序、云部署、容器镜像等)见同目录《开发环境与支撑软件建设管理方案》第五节“构建与发布形态”及第九节容器与 CI/CD。

一、Python 与 JS 对比(一眼看清差异)
环节 | Python | JS(npm/pnpm) |
包描述文件 | pyproject.toml 或 setup.py | package.json |
版本号写在哪 | pyproject.toml 里 version = "1.0.0" | package.json 里 "version": "1.0.0" |
打包命令 | python -m build → 生成 .whl + .tar.gz | npm pack → 生成 .tgz;或 pnpm publish 发到 registry |
发布到本机 | 不“发到服务器”,其它项目用 本地路径 或 本地目录索引 安装 | 不“发到服务器”时,其它项目用 本地路径 或 file: 安装 |
其它工程如何安装 | pip install -e D:\path\to\组件 或 pip install ./dist/xxx.whl 或 pip install --index-url file:///D:/my-packages/ 包名 | npm install D:\path\to\组件 或 npm install file:../组件 或 私有 registry |
开发时边改边用 | pip install -e .(可编辑安装,改代码即生效) | pnpm link 或 npm link 或 依赖里写 "xxx": "file:../xxx" |
核心区别:Python 用 pip + pyproject.toml/wheel;JS 用 npm/pnpm + package.json。本机“发布”都是把包放在本机某目录或直接用路径,其它工程通过路径或本地索引安装。

二、Python 通用技术组件:从零到被其它工程安装
2.1 目录与文件结构(最小可用)
假设你的 Python 通用组件叫 zzpy-common,放在本机目录 D:\work\pys\zzpy-common(或你喜欢的路径):
D:\work\pys\zzpy-common\├── pyproject.toml# 包名、版本、依赖、入口(必写)├── src/│└── zzpy_common/# 包名(建议与 pyproject 里 name 对应,下划线)│├── __init__.py│└── 你的模块.py├── README.md# 可选└── 其他代码/测试
·包名:pyproject.toml 里用 name = "zzpy-common"(可带连字符);导入名 用目录名 zzpy_common(下划线),例如 import zzpy_common。
2.2 写 pyproject.toml(核心)
在 D:\work\pys\zzpy-common\pyproject.toml 里放(按需改名字和版本):
[build-system]requires=["setuptools>=61", "wheel"]build-backend="setuptools.build_meta"[project]name="zzpy-common"version="1.0.0"description="Python 通用技术组件"readme="README.md"requires-python=">=3.9"dependencies=[# 这里写你的依赖,如 "requests>=2.28",][project.optional-dependencies]dev=["pytest", "black"][tool.setuptools.packages.find]where=["src"]
·版本号:改 version = "1.0.0" 即可(建议语义化:主.次.修订)。
·依赖:其它工程 pip install zzpy-common 时会自动装这里列的 dependencies。
2.3 打包(生成可发布文件)
在本机进入组件目录,执行:
cd D:\work\pys\zzpy-commonpython -m pip install build --userpython -m build
·会生成 dist/ 目录,里面有 zzpy_common-1.0.0-py3-none-any.whl 和 zzpy_common-1.0.0.tar.gz。
·每次改版本号后重新执行 python -m build 即可。
2.4 “发布到自己的电脑”(三种用法任选)
方式 A:其它工程直接用本地路径安装(最省事)
在要使用该组件的项目里(任意目录,任意虚拟环境):
pip install -e D:\work\pys\zzpy-common
·-e 表示“可编辑安装”:你改 zzpy-common 里的代码,该工程里 import zzpy_common 会立刻用最新代码,无需重新打包。
·适合本机多项目共用、边开发边用。
方式 B:从打包好的 wheel 安装(固定版本)
把 dist/*.whl 拷到某目录(如 D:\work\pys\packages\),在其它工程里:
pip install D:\work\pys\packages\zzpy_common-1.0.0-py3-none-any.whl
·适合“发一个版本,多台机子或多次安装同一版本”。
方式 C:本机目录当“简易索引”(多包时方便)
1.把多个组件的 dist/*.whl 都放到同一目录,如 D:\work\pys\packages\。
2.其它工程里: pip install --index-url file:///D:/work/pys/packages/ zzpy-common(Windows 下 file:/// 后跟本地盘符路径。)
·适合本机有多个自研 Python 包、想用“类似私服”的方式装。
2.5 其它工程里怎么用
安装完成后,在任意 Python 代码里:
import zzpy_common# 或from zzpy_common.你的模块 import 某函数
·若用 pip install -e D:\work\pys\zzpy-common,依赖已在安装时装好;若用 wheel 安装,依赖也会按 pyproject.toml 安装。

三、JS 通用技术组件:对比理解(本机发布)
·打包:在组件目录执行 npm pack 或 pnpm pack,会生成 zz-common-1.0.0.tgz。
·其它工程安装:
o本机路径:npm install D:\work\js\zz-common 或 "zz-common": "file:../zz-common" 写在 package.json 的 dependencies。
o或把 .tgz 拷过去:npm install ./zz-common-1.0.0.tgz。
·开发时边改边用:pnpm link 或依赖里写 "file:../zz-common"。
和 Python 的对应关系:package.json ≈ pyproject.toml,npm pack ≈ python -m build,npm install ≈ pip install -e。

四、实操清单:只做 Python 组件时
可选:用现成模板本仓库已带一个最小模板,可直接复制到本机再改名字和代码:
·模板路径:doc/开发工作/Zz开发工作/python-package-template/
·复制到本机,例如:复制整份 python-package-template 到 D:\work\pys\zzpy-common(可重命名文件夹为你的组件名),然后按下面步骤 2~5 操作。
步骤:
1.建目录:若不用模板,则本机新建目录,如 D:\work\pys\zzpy-common,里面建 src/zzpy_common/ 和 __init__.py、你的模块。
2.写 pyproject.toml:复制上面 2.2 的模板,改 name、version、dependencies。
3.打包:python -m pip install build,再 python -m build。
4.在其它工程安装:到目标项目目录(建议先建 venv),执行 pip install -e D:\work\pys\zzpy-common。
5.验证:在目标项目里 import zzpy_common 或 from zzpy_common.xxx import ...,能跑即成功。
之后每次改版本:改 pyproject.toml 里 version,再执行一次 python -m build;其它工程若用 pip install -e 则无需重装(已是“活”的链接),若用 wheel 安装则需重新 pip install 新 wheel。

五、应用打包:打成可执行/可部署的应用
本节按两条线说清应用打包:一是仍是源码、在解释器下运行;二是打成独立可执行文件、直接运行。两条线本质不同:前者目标机需要对应运行时(Python/Node),后者不需要。下面先给出通用的“入口”概念,再分 Python、JS 两条线分别说明:怎么打包、有哪几种形态、打完怎么执行。与“库/组件包”的区别见 0.14。

5.1 入口与主程序(通用)
应用要能“跑起来”,必须有一个入口(主程序):进程从哪一段代码开始执行。
·Python:单文件入口(python 路径/main.py)、包入口(包内 __main__.py,执行 python -m 包名)、或通过安装生成的脚本命令(pyproject.toml 里 console_scripts)。多入口时执行哪一个由用户调用的命令决定;打成 exe 时一般一个 exe 对应一个入口,多入口则打多个 exe。
·JS/Node:运行某脚本即入口(node 入口.js 或 npm start 里写的脚本);package.json 的 main、bin、scripts 约定默认入口或多条命令。多入口由用户执行的命令或打的多个 exe 区分。
小结:入口由项目配置 + 用户执行的命令共同决定;多主程序不会“自动”选一个,而是显式指定(不同命令或不同 exe)。

5.2 Python 应用打包:两条线
你的理解是对的:Python 应用打包确实分两条线——一条本质仍是 Python 代码、在 Python 解释器下运行;另一条已经打成 exe(或等价的可执行文件),直接执行、无需本机安装 Python。下面按两条线分别交代:有哪几种形态、怎么打包、打完怎么执行。

线一:仍是 Python 代码,在解释器下运行
本质:交付物是源码(或源码 + 已安装好的依赖),在目标机上由已安装的 Python 解释器执行。没有“编译成 exe”,运行方式与开发时一致。
有哪几种形态
形态 | 交付物 | 目标机需要 |
源码 + 依赖清单 | 源码目录 + requirements.txt(或 pyproject.toml) | 安装 Python,自建 venv,执行 pip install -r requirements.txt 后运行 |
可分发目录 | 源码 + 已装好依赖的虚拟环境(或 site-packages 拷贝) | 安装与打包时同版本 Python,或使用目录内自带的解释器(若打包时一并放入) |
容器镜像 | 镜像内包含 Python、依赖和源码,入口由镜像的 CMD/ENTRYPOINT 指定 | 仅需 Docker(或兼容运行时),无需本机装 Python |
怎么打包
·源码 + 依赖清单:整理好源码和入口;用 pip freeze 或项目依赖声明生成依赖清单;打包为压缩包或从仓库拉取即可。
·可分发目录:在打包机上建 venv、安装依赖,将“源码 + 该 venv”或“源码 + site-packages”打成目录或压缩包;若希望目标机不装 Python,需把解释器本身也打进目录(较少见,一般仍要求目标机有同版本 Python)。
·容器镜像:在 Dockerfile 中基于 Python 镜像安装依赖、拷贝源码,并设置启动命令为 python -m 包名 或 python 入口.py。
打完怎么执行
·目标机必须有与开发/打包时兼容的 Python 版本(除容器外,容器内已含)。
·执行方式:在项目根或约定目录下执行 python -m 包名 或 python 路径/入口.py;若用 venv,先激活再执行;若用容器,执行 docker run 镜像名。
·工作目录:相对路径(如 ./config.json)相对的是启动进程时当前所在目录(cwd),不是脚本文件所在目录;因此需在文档中约定“从哪个目录启动”(如“在项目根下执行”)。多入口时,由用户执行不同命令(如 python -m app.server 与 python -m app.cli)选择。

线二:独立可执行文件,无需本机 Python
本质:把 Python 解释器(或运行时)+ 应用字节码 + 依赖 打成一体,产出独立可执行文件(如 .exe)。目标机直接运行该文件,不需要安装 Python。
有哪几种形态
形态 | 交付物 | 目标机需要 |
单文件可执行 | 一个 exe(或等价二进制),依赖与运行时内嵌 | 无需 Python,直接运行 exe |
目录型可执行 | 一个主 exe + 同目录下若干依赖文件(dll、库等) | 无需 Python,从该目录运行主 exe |
怎么打包
·在打包机上需要:已安装 Python、应用及其依赖(建议专用 venv)、以及“冻结/打包”类工具(把解释器与代码打成 exe 的这类工具)。
·打包时指定入口:指定一个入口脚本或入口模块(如 包名 的 __main__.py);一个入口对应一个 exe。若有多个主程序,需分别指定不同入口、打多次,得到多个 exe。
·产出:单文件则得到一个 exe;目录型则得到一个目录,内含主 exe 与依赖文件。必要时把配置文件、资源文件等放在 exe 同目录或约定子目录,并在文档中说明。
打完怎么执行
·目标机不需要安装 Python。将单文件 exe(或整份目录)拷贝到目标机即可。
·执行方式:直接双击 exe,或在命令行从 exe 所在目录(或文档约定的工作目录)运行该 exe。相对路径通常以“启动时当前目录”为准,若希望相对 exe 所在目录,需在代码里按“可执行文件路径”解析;文档中应写明工作目录约定。
·多入口:每个入口对应一个独立 exe,用户运行不同 exe 即执行不同主程序。

Python 两条线小结
线 | 本质 | 目标机是否要 Python | 典型执行方式 |
线一 | 仍是 Python 代码,解释器执行 | 要 | python -m 包名 或 python 入口.py(或容器 docker run) |
线二 | 独立可执行文件 | 不要 | 直接运行 exe |

5.3 JS/Node 应用打包:两条线(简述)
逻辑与 Python 类似,分两条线即可说清。
线一:仍是 JS 代码,在 Node 下运行
·本质:交付物是源码(或构建产物)+ 依赖(如 node_modules),在目标机上由已安装的 Node 执行。
·形态:源码 + 依赖清单(目标机 npm ci);或可分发目录(源码 + node_modules);或容器镜像内装 Node 与依赖。
·怎么执行:目标机需安装 Node;在约定目录执行 node 入口.js 或 npm start;容器则 docker run。多入口由 package.json 的 scripts 与用户执行的命令决定。
线二:独立可执行文件,无需本机 Node
·本质:把 Node 运行时 + 应用代码打成单 exe(或桌面应用 exe),目标机直接运行,无需安装 Node。
·形态:单文件 exe(如通过 pkg 等);或桌面应用 exe(如 Electron:主进程 + 渲染进程,自带运行时)。
·怎么执行:直接运行 exe;多入口则打多个 exe 或通过 exe 命令行参数在内部区分。
