Skip to main content

kkpack

把 Python 项目打包成 Windows 上自带解释器的 exe,第三方依赖在目标机首次运行时按需安装。

pip install kkpack
kkpack main.py

使用者电脑上不需要装 Python,也不需要装任何依赖。


它解决什么问题

传统打包(Nuitka / PyInstaller 一把梭)有两个痛点:体积 —— 把 numpy、torch 这类重型库全编进 exe,产物动辄几百 MB、编译半小时起步;更新 —— 改一行代码就要重编整棵依赖树。

kkpack 的做法是:你的代码 + Python 运行时 + 标准库编进 exe,第三方依赖留到运行时按需安装。 依赖版本在打包时冻结成一份完整清单(含间接依赖),所以目标机装出来的版本永远和你开发时一致。


30 秒上手

就是最普通的项目结构,不需要为打包改任何代码:

myapp/
├── main.py
├── mypkg/
│   ├── __init__.py
│   └── core.py
└── requirements.txt      # requests==2.31.0
pip install kkpack        # 第 1 步:安装 kkpack
cd myapp
kkpack doctor             # 第 2 步:体检环境(可选,第一次建议跑)
kkpack main.py            # 第 3 步:打包

doctor 会告诉你:有没有 C 编译器、后端装没装、requirements.txt 在不在,以及哪些依赖没写版本、哪些用了范围约束(两者都允许,只是打包时取到的版本不同)。

第 4 步:把产物整个目录拷给别人

dist/main.dist/main.exe          # 双击即可运行
dist/main.dist/requirements.txt  # 依赖清单,随程序分发

main.exe 首次运行会把依赖装到 exe 同级的 _deps/ 目录里,之后每次启动都不再联网。 注意:main.dist 整个目录要一起拷,不能只拷 exe。


命令行

kkpack [入口文件] [选项]
kkpack init           生成带注释的配置文件
kkpack doctor         检查当前环境的打包能力
参数 作用 默认
entry 入口 py 文件,如 main.py(不写则从配置读 tool.entry) —
--backend nuitka|pyinstaller 打包后端,没装会自动 pip 安装 nuitka
--mode runtime|offline|all 依赖处理方式 runtime
--onefile / --no-onefile 单 exe / 目录形式(目录启动更快,推荐) 目录形式
--windowed / --console 是否显示控制台黑窗口(GUI 程序用 --windowed) --console
--out DIR 产物输出目录 dist
--exe-name NAME 产物(exe)名,不用写 .exe 入口文件名
--icon PATH.ico 程序图标,只支持 .ico 系统默认图标
--index URL 追加镜像源,可重复(--index A --index B) 阿里云→清华→PyPI
--stdlib precise|full|none 标准库包含策略 full
--jobs N 并行编译进程数,内存小就调小 2
--callable NAME 模块模式下要调用的函数名 —
-c, --config PATH 指定配置文件(默认自动找 kkpack.toml / pyfrost.toml) 自动查找
--clean 清空自动生成的构建文件后重编(保留 wheel 下载缓存) —
--quiet 只输出关键结果 —

优先级:命令行参数 > 配置文件 > 默认值。


配置文件(可选,不写也能跑)

kkpack init          # 生成一份带注释的样板

默认查找当前目录下的 kkpack.toml(本工具前身用过的 pyfrost.toml 同样接受)。 下面这份 全部可以省略:

[tool]
backend = "nuitka"        # nuitka | pyinstaller
entry = "main.py"         # 命令行给了入口就以命令行优先
exe_name = "MyApp"        # 产物(exe)名,不用写 .exe;默认取入口文件名
icon = "assets/app.ico"   # 程序图标,只支持 .ico;不写用系统默认图标
onefile = false           # 目录形式启动更快
console = true            # GUI 程序改成 false,不弹黑窗口
output_dir = "dist"
jobs = 2                  # 并行编译数,内存不够就调小

[bundle]
mode = "runtime"          # runtime | offline | all,见「依赖处理的三种模式」
include = []              # 强制编译进 exe 的包
exclude = []              # 强制留到运行时的包

[runtime]
check_update = false      # 是否在运行时检查依赖更新(会联网)
progress = true           # 首次安装时弹 tkinter 进度条
download_jobs = 4         # 并发下载数

[index]
urls = [
    "https://mirrors.aliyun.com/pypi/simple/",
    "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/",
    "https://pypi.org/simple/",
]
timeout = 30

[stdlib]
# full(默认):整个标准库打进 exe,含 tkinter / sqlite3 / asyncio
# precise     :只补真正用到的标准库,体积更小;代价见「依赖处理的三种模式」
include_mode = "full"

[version]                 # 见「版本信息与代码签名」
company = "某某科技有限公司"

[sign]                    # 见「版本信息与代码签名」
certificate = ""          # 留空 = 不签名

配置文件里写了 kkpack 不认识的段或键,构建时会明确提示被忽略,不会出现"改了没生效"。


requirements.txt 是唯一的依赖来源

依赖 只写在 requirements.txt 里,不要在配置文件里重复声明一遍 —— 避免两处版本号不一致这种最难查的问题。

版本 不强制锁定,三种写法都收:

写法 构建期行为 建议
pyserial 取 当时的最新版 允许。构建日志会给出该补的锁定行
pyserial>=3.4 取满足约束的最新版 允许。约束在构建期有效
pyserial==3.5 就用这一版 推荐 —— 每次构建结果完全一致

三种写法最终都会被 解析成一个具体版本 并冻结进产物:dist/*.dist/requirements.txt 与运行期清单 REQUIREMENTS 里都是 name==version。所以 目标机装到的永远是同一份依赖,差别只在"下一次构建会不会得到另一个版本"。构建日志会直接给出可粘贴的锁定行:

没写版本,按最新解析:pyserial==3.5
推荐锁定(写回 requirements.txt 即可让每次构建结果一致):pyserial==3.5  pika==1.3.2

打包时会把它 展开成完整依赖树(包括 urllib3 / certifi 这类间接依赖),展开后的清单随 exe 分发 —— 你在开发机看到的依赖,就是目标机实际跑的那一份。不支持 -r other.txt、git+...、VCS 与本地路径依赖。


依赖处理的三种模式

模式 产物体积 目标机首次启动 适用场景
runtime 最小 需要联网安装依赖 默认,绝大多数情况
offline 中(多了 offline/ 目录) 不联网,从随包 wheel 安装 内网 / 客户机不能联网
all 最大 不联网,依赖已在 exe 里 极端自包含需求(编译很慢,实测不划算)

构建机至少要联网一次 —— 三种模式的差别只在 目标机,打包时必须先展开依赖树(urllib3 / certifi 这类间接依赖也要一起锁死)。runtime 模式下这一步 只取元数据、不下载 wheel,结果缓存在 .kkpack/ 下,--clean 也不会删。

三种情况例外,会真把 wheel 取到本地(构建日志里会写明是哪一条):

配置 为什么非下不可
[stdlib] include_mode = "precise" precise 要扫 wheel 里的 import,才知道该补哪些标准库模块
mode = "offline" wheel 要随程序一起分发出去
mode = "all" wheel 要解压出来编译进 exe

精细控制:默认全部依赖走"运行时安装",用 [bundle] include / exclude 可以单独指定谁进 exe。优先级 exclude > include > mode;include 里写 "*" 等价于 mode = "all"。

[bundle]
include = ["pillow"]      # pillow 编译进 exe,其余运行时安装
exclude = ["heavy-tool"]  # heavy-tool 一定不进 exe

runtime 模式为什么也会下载 wheel:precise 的代价

full(默认)把整个标准库塞进 exe,不需要知道你的代码用了哪些标准库模块,只看依赖元数据就够,一个 wheel 都不用下载。 precise 要算出"只补真正用到的那些",就必须知道 每个第三方包自己 import 了什么 —— 这些信息只存在于 wheel 的源码里,于是 kkpack 只能先把 wheel 取到 .kkpack/wheels/,再逐个解压、AST 扫描(实现见 backends.ast_stdlib_modules)。

实测典型工程 PySide6 + pyqtgraph + scipy(Python 3.9 / win_amd64)改成 precise 后,.kkpack/wheels/ 里会一次性出现约 267 MB:

wheel 大小 它 import 的标准库(实测片段)
PySide6_Addons 123.0 MB asyncio、contextvars、concurrent
PySide6_Essentials 78.9 MB logging、argparse、ast
scipy 46.2 MB itertools、warnings、math
numpy 15.9 MB subprocess、zipfile、operator
pyqtgraph / shiboken6 / PySide6 1.9 / 1.1 / 0.5 MB weakref、marshal、base64

这不是"runtime 失效了",也不是重复下载,而是 precise 换取更小 exe 的 必要成本。怎么区分正常与异常:日志里有一行 必须把 wheel 取到本地:… 就是正常;看到 pip download 的输出却 没有 这一行,那才是真出了问题。wheel 下过一次就留在 .kkpack/wheels/(--clean 也不删),重复构建不会重下。

想省掉这次下载只有把 include_mode 改回 full(代价是 exe 里带着整个标准库)。"exe 更小"与"构建期不下 wheel"目前只能二选一。


GUI 程序(没有黑窗口)

kkpack main.py --windowed

或在配置里 [tool] console = false。这会转成 Nuitka 的 --windows-disable-console / PyInstaller 的 --noconsole。

关掉黑窗口后 print 都不再可见,所以 kkpack 在 依赖安装这一段时间 用 tkinter 弹一个进度条窗口:下载(显示已下载 MB 数与包进度)→ 安装(逐个包)→ 装完自动关闭,接着启动你的程序。

位置 说明
[runtime] progress = true/false 是否弹进度条(默认 true)
环境变量 KK_PROGRESS=0 单次运行临时关掉(0 / no / off / false 都认)
tkinter 不可用 自动退回控制台输出,不影响安装

两点容易误会:

  • 进度条和有没有黑窗口无关。 console = true(默认)时 一样会弹 —— 黑窗口里是逐行文字输出,进度条窗口是图形反馈,两者同时存在、互不冲突。有黑窗口又不想弹窗,就把 [runtime] progress 设成 false。
  • 依赖已经装好时不弹。 _deps/ 里存着安装记录就整段跳过,既不弹窗也不联网 —— 也就是说 只有"确实有包要装"的那一次运行才会弹,之后每次启动都是安静的。

想确认弹框到底有没有生效:[tool] console = true 打成黑窗口版再跑一次首次安装,能同时看到 tkinter 进度条和它前面的 [kkpack] 需要安装 N 个包…;如果看到的是 [kkpack] 依赖已就绪:N 个包已安装,跳过网络请求,说明依赖早就装好了,本来就不该弹框。 实测参照:Windows x64 + Python 3.9 + Nuitka 2.7.13,console = true 的 PE 子系统为 Console(3) 时进度条照常显示(窗口类 TkTopLevel)。

打包 Qt / PySide6 程序

PySide6、PyQt5/6、pyqtgraph 这类 Qt 应用不需要额外参数,kkpack main.py 直接可用。下面三件事 kkpack 已经替你处理好了,每一条都是真机踩出来的:

  1. --nofollow-import-to 的模块名不能带斜杠。PySide6 官方 wheel 的 top_level.txt 里写的是路径(PySide6/Qt3DCore),原样传给 Nuitka 会直接 FATAL: ... not directory path。kkpack 会统一规范成点号(PySide6.Qt3DCore)。
  2. 不能把 built-in / frozen 模块当成 --include-module。_abc / _winapi / zipimport 这类由解释器本体提供、没有文件可 include;旧版 Nuitka(< 2.7)收到会在解析 include 列表时内部崩溃(os.path.abspath(None))。kkpack 按 _imp.is_builtin / is_frozen 先把它们摘掉。
  3. 产物根目录要带 python3.dll。cp39-abi3 这类轮子(PySide6 / shiboken6)的扩展链接的是稳定 ABI 转发层 python3.dll,不是 python39.dll;目标机上没有 Python,缺它就报 ImportError: DLL load failed while importing Shiboken: 找不到指定的模块。kkpack 会从构建机把它一并带进产物。

实测项目:PySide6 6.7.2 + pyqtgraph 0.13.7 + numpy 2.0.2 + pygame + pyserial + pika,打包后运行 exe,Qt 窗口正常显示、事件循环正常退出。

Qt 的插件(platforms/qwindows.dll 等)随 wheel 一起装在 _deps/ 里,所以 别把 _deps/ 只当缓存随手清(删了下次启动会重新联网装一遍)。

进度条文案可配置

[progress] 段可以按项目改语言;占位符写错会自动退回默认文案,不会让安装崩掉:

[progress]
title = "Downloading components"
downloading = "Downloading {done}/{total}..."
installing = "Installing..."
format = "{mb:.1f} MB downloaded ({done}/{total})"
installed = "Installed {name}"

可用键:title / prepare / downloading / installing / format / percent / installed / package / cancel / cancelling / cancelled / failed_title / failed_body / sources_tried / missing_files / offline_hint / enter_to_exit。 带占位符的四个:format({mb} 已下载 MB、{done}、{total})、percent({value} 百分比、{done}、{total})、installed({name} wheel 文件名)、package({name}、{version})。

依赖安装中途能取消

进度窗口右下角有一个 取消 按钮,它和窗口右上角的 X 是同一件事:立刻停掉下载、结束进程。

  • 已经装好的包 保持有效(记录在 _deps/<py版本-平台>/_installed.json 里),下次启动只补没装完的那些。
  • 中断的那一刻正在解压的包不会留下记录,下次启动会重新解压它 —— 解压本身可重复,不会留下坏状态。
  • 退出码是 130(128 + SIGINT),和控制台里按 Ctrl-C 的约定一致。
  • 没有进度窗口时(progress = false 或 KK_PROGRESS=0),在控制台按 Ctrl-C 走的是同一条路。
  • 取消是"直接结束进程",而不是慢慢等它停下:取消的那一刻下载线程多半正卡在 socket 的 recv 上(下大 wheel 时是常态),而 concurrent.futures 注册了 atexit,正常退出会去 join 这些线程 —— 实测能一直拖到下载超时(几十秒)。kkpack 的做法是先给工作线程 1 秒自己收尾,超时就直接退出。

产物名与图标

[tool]
exe_name = "MyApp"          # 产物(exe)名,不用写 .exe
icon = "assets/app.ico"     # 只支持 .ico,相对路径按项目根目录解析

等价命令行:kkpack main.py --exe-name MyApp --icon assets/app.ico。

产物名 默认取入口文件名(main.py → main.exe),可以随便起,含连字符、空格、点、中文都能用:My-App、app.v2、我的工具 都没问题。会被拒绝的只有 Windows 本身不允许的:\ / : * ? " < > |、保留设备名(CON / PRN / AUX / NUL / COM1-COM9 / LPT1-LPT9)、首尾是点号的名字。这些在构建一开始就明确报错,不会等编译器抛一堆看不懂的日志出来。

为什么含 - 的名字要特殊处理? Nuitka 在 目录形式(onefile = false)下会忽略 --output-filename,产物名只能取自入口文件名,而这个文件名同时会被解析成模块名,My-App 会直接解析失败。kkpack 的做法是:编译期用安全模块名,构建成功后再把目录里那个 <模块名>.exe 改名为你要的名字。所以产物名完全不受模块命名规则限制,你只需要在构建日志里看到一行"产物已按 exe_name 改名"就知道生效了。

改名是 四种组合统一 的(Nuitka / PyInstaller × 单文件 / 目录):

形态 编译后改名
单文件 dist/<模块名>.exe → dist/<exe_name>.exe
目录形式 dist/<模块名>.dist/<模块名>.exe → dist/<模块名>.dist/<exe_name>.exe

只改 exe 的名字,产物目录名不变。 目录名保持后端的模块名(<模块名>.dist)—— 那是后端自己覆盖、自己维护的目录,改它只会多出一份"上一轮残留"要处理;而目录名对使用者没有意义,拷给别人的是目录里的那个 exe。

图标 必须是 .ico(Windows 可执行文件的图标格式)。给 png 之类的文件会在构建开始时报错并提示转换,不会等到编译结束才失败。图标会被编译进 PE 资源区。

首次运行的下载进度窗口也会用这个图标。 tkinter 的窗口图标只能从一个 .ico 文件加载(它拿不到 exe 自己的 PE 资源),所以构建时会 再复制一份图标到 exe 同级:

dist/main/
├── main.exe          # 图标已编进 PE 资源
└── app.ico           # 同一份图标,供进度窗口使用

单文件(onefile = true)也一样会多出这个 .ico —— 这是让进度窗口不顶着 Tk 默认羽毛图标的唯一办法(onefile 的临时解压目录会被运行期主动排除,打进 exe 反而找不到)。不配置就用系统默认图标,进度窗口也不设图标,不影响其它功能。


版本信息与代码签名

这两件事只为一个目的:让 Windows 和安全软件知道"这个 exe 是谁做的"。系统里能读到这些信息的只有两处 ——「属性 → 详细信息」里的版本资源,和数字签名。两处都空着的未签名 exe,在安全软件的评分模型里就是个高分可疑文件(见下一节)。

[version]:写进 exe 的 PE 版本资源

[version]
company = "某某科技有限公司"            # CompanyName
product = "某某工具"                    # ProductName,省略 = exe 名
description = "某某工具主程序"          # FileDescription,省略 = exe 名
version = "1.2.0"                       # FileVersion / ProductVersion
copyright = "版权所有 (C) 2026 某某科技有限公司"
trademark = ""

不写也能构建,兜底值保证这一栏 不会是空的:产品名和描述退回 exe 名,版本退回 0.0.0。唯一不编造的是公司名 —— 在别人的程序里塞一个不存在的公司名,比这一栏空着危险得多;所以完全没配时,构建日志里会有一条提示让你补 company。

  • version 最多 4 段数字(1.2 / 1.2.0 / 1.2.0.4),可以带 v 前缀。Windows 的版本资源本来就是 4 个 16 位整数,所以 1.2.3-beta 只能写进去 1.2.3 —— 丢掉的后缀会 单独提示出来,不静默截断。
  • 两个后端写法不同、结果一致:Nuitka 逐项传 --windows-company-name 这类参数;PyInstaller 走一个自动生成的 VSVersionInfo 文件(.kkpack/version_info.txt,纯 ASCII、中文按 \uXXXX 转义,免得不同版本的 PyInstaller 在文件编码上打架)。
  • 只在 Windows 上生效 —— 其它平台本来就没有这一栏。

[sign]:构建末尾自动签名

[sign]
certificate = "C:/certs/app.pfx"           # 留空 = 不签名(默认)
password = ""                              # 留空则读环境变量 KK_SIGN_PASSWORD
timestamp_url = "http://timestamp.digicert.com"
signtool = ""                              # 留空自动找 PATH 和 Windows SDK

certificate 填了,打包的 最后一步 就会自动签名;留空就整段跳过、不碰签名。三种写法对应 signtool 的三个参数:

写法 传给 signtool 什么时候用
C:/certs/app.pfx /f + /p 有证书文件(相对路径按项目根目录解析)
CN=某某科技有限公司 /n 证书已装进「个人」证书存储(硬件令牌、云签名客户端都在那)
40 位十六进制指纹 /sha1 存储里同名证书有多张,需要精确指定

几个刻意的设计:

  • 签名在改名之后。 产物名是编译完后改出来的(见上一节),签名必须落在最终产物上。
  • 失败会让构建失败。 静默发出一份"以为已经签好"的 exe 才是最坏的结果,所以签不上会明确报错并说清原因。临时要跳过就设 KK_SIGN=0,或把 certificate 留空。
  • 密码为空也会显式传 /p ""。 signtool 缺 /p 会弹一个 GUI 密码输入窗口,在无人值守的构建里就是永久挂起;真挂住了还有超时兜底会报错退出。
  • 签名后自动校签(signtool verify /pa)。自签名证书或证书链没装全时校签会报错,但这不影响签名本身有效 —— 所以校签不过只提示、不判失败。
  • 时间戳别忘了。 不签时间戳的话,证书一过期,之前签过的所有版本签名会同时失效。
  • 需要 signtool.exe,它随「Windows SDK」的 Signing Tools 组件安装(几十 MB,不必装整个 SDK 的编译器)。找不到时会提示装哪个、或把路径写进 signtool。云签名(Azure Trusted Signing 等)一般也提供 signtool 兼容的调用方式。

杀软误报怎么办

exe 被 Defender / 360 / 火绒拦下甚至直接删掉,绝大多数情况不是程序有问题,而是 它的形态和恶意软件太像。按下面的顺序从便宜到贵地处理。

先分清是哪种:被隔离/被删(提示 Trojan:Win32/xxx!ml 这类,看「Windows 安全中心 → 保护历史记录」)是杀软误报;弹窗问是否允许联网 是防火墙在问,两者对策完全不同。检测名带 !ml 结尾说明是机器学习打的分、不是特征库命中 —— 这种最好治。

1. 改打包配置(零成本,今天就能做)。kkpack 的默认值是 通用 的,不是 最不容易被误杀 的。按收益从高到低:

措施 为什么 代价
onefile = false 单文件每次运行都要把自己解压到 %TEMP% 再执行 —— 这正是杀软眼里的"释放器"行为 分发变成目录
mode = "offline" / all runtime 模式会在 首次运行时 联网下载、解压、再 import,这条"下载即执行"链是行为监控最敏感的 产物变大
填 [version] company 没有公司名的未签名 exe 只能靠文件内容猜 一行配置
填 [version] version 0.0.0 比一个正经版本号更像没人维护的野程序 一行配置
换 backend Nuitka 的 stub 与 PyInstaller 的 bootloader 被打包器连坐拉黑时,换一个往往直接绕开 重编一次

console / progress 这些和误报无关,别在这上面花时间。

2. 误报申诉(免费,几小时到几天)。微软:https://www.microsoft.com/en-us/wdsi/filesubmission,选 software developer → false positive,会回复检测名和处理结论。国内 360 安全开放平台、腾讯电脑管家、火绒各有独立入口,要分别提。

别把样本传 VirusTotal 求"清白"。 VT 会把样本共享给各家引擎,常见效果是让更多杀软更快把这一版拉黑。它适合查已有哈希,不适合提交自己的新版本。

3. 代码签名(治本)。见上一节。预期要说清:签名不是立刻免死金牌 —— OV 证书要靠下载量攒 SmartScreen 信誉,EV 证书才是即时信任;个人开发者申请 OV/EV 通常需要企业资质。但签了之后,"未签名"这个最强的负分项就消失了。

4. 换个分发形态。用 Inno Setup / NSIS 把 xxx.dist/ 打成安装包(装到 Program Files、写卸载项)。行为画像立刻从"从临时目录跑起来的裸 exe"变成"正常安装的软件",顺带解决单文件往 %TEMP% 释放文件的观感问题。


目标机上的行为

xxx.dist/
├── xxx.exe
├── requirements.txt          # 冻结的依赖清单
├── _deps/                    # 首次运行时生成(按 py版本-平台 分目录)
│   └── py39-win32-amd64/
└── offline/                  # 仅 offline 模式有:随包 wheel
  • 依赖装进 _deps/<py版本-平台>/,不会污染目标机的 Python 环境(也没有 Python 环境)。
  • C 扩展自带的 DLL 目录(如 numpy.libs)会自动注册,multiprocessing 子进程也会继承。
  • 全部依赖都下不到时,会打印(或弹窗)缺哪些包 + sha256 + 直链 + 离线目录,把 wheel 放进 offline/ 目录重启即可,不会悄悄崩掉。

多进程(multiprocessing)

spawn / Pool 的写法可以直接打包,不用改代码,也不用自己调 freeze_support():

import multiprocessing as mp

def _worker(n):                       # 定义在入口文件里也没问题
    import numpy as np
    return int(np.arange(1, n + 1).sum())

def main():
    ctx = mp.get_context("spawn")     # Windows 上没有 fork,用 spawn 最稳
    with ctx.Pool(processes=2) as pool:
        print(pool.map(_worker, [4, 5]))

if __name__ == "__main__":
    main()

冻结后,spawn 的子进程会以 exe --multiprocessing-fork 的形式重启 exe。kkpack 在运行期初始化时识别出它,自动完成三件事:

  1. 跳过依赖安装 —— 沿用父进程算好的 _deps 目录(否则 onefile 每次解压目录不同,子进程会装到又一个新地方)。
  2. 重新注册 DLL 目录 —— os.add_dll_directory 是进程级的、不会继承,numpy 这类带 .libs 的 C 扩展必须在子进程里再注册一次。
  3. 把入口文件里的函数与类补回 __main__ —— Windows 冻结后 multiprocessing 不会重建 __main__(见 multiprocessing.spawn 里的 WINEXE 分支),少了这一步子进程会报 Can't get attribute '_worker' on <module '__main__' (built-in)>。

唯一约定:worker 函数要放在 能被 import 的模块里或入口文件顶层,不要藏在 if __name__ == "__main__": 内部 —— pickle 按引用序列化时会取不到它。


运行期环境变量(排障/临时覆盖用)

变量 作用
KK_INDEXES 逗号分隔的镜像源,覆盖打包时配置的源列表
KK_OFFLINE 指向离线 wheel 目录
KK_PROGRESS 设为 0 关闭进度条窗口
KK_TRACE 设为 1 把每次 bootstrap 写进 _kk_trace.log,用来查子进程有没有重跑

常见问题

Q:必须要 requirements.txt 吗? 是,且不能为空。它是依赖的唯一来源,没有它 kkpack 不知道目标机该装什么。 (完全没有第三方依赖的项目,暂不支持——后续版本会放开。)

Q:runtime 模式打包时会下载 wheel 吗? 默认不会(.kkpack/wheels/ 是空的)——[bundle] include 里的包例外,那些必须 下载、要解压出来编译进 exe。解析完整依赖树靠 pip 的 --dry-run / --report: 只取每个包的 .metadata(KB 级),直链与 sha256 由 pip 的 report 给出, 再按文件名从你配的镜像页补一份镜像直链。 两个前提 kkpack 自己兜住了:构建环境的 pip 低于 22.2 时,另外装一份新的到 .kkpack/pipenv 供解析使用(不升级构建环境本身的 pip —— 那份还要用来跑你的 构建,就地升级在 Windows 上会把环境 pip 装成半个);源不支持 PEP 658 元数据时 (阿里云、清华目前都不支持)自动用官方 PyPI 兜底解析,下载仍然优先走你配的镜像。 两件事都可以在 [index] 里关掉:upgrade_pip = false / pypi_fallback = false, 关掉后 pip 太老或源不支持就只能退回"把 wheel 下载到本地"。

Q:改成 include_mode = "precise" 之后,runtime 模式为什么又下载了 200+ MB wheel? 因为 precise 要拆开每个 wheel 看它 import 了哪些标准库模块,才能算出该往 exe 里补哪些, 所以必须先把 wheel 取到 .kkpack/wheels/。这是 precise 的固有代价,不是 runtime 失效,也不是重复下载。详见 runtime 模式为什么也会下载 wheel。 想省掉这次下载就改回 include_mode = "full"。

Q:首次运行时进度窗口关不掉 / 想中断下载? 现在 可以:窗口右下角的 取消 按钮和右上角的 X 是同一个动作 —— 立即停掉下载并结束进程(退出码 130)。已经装好的包保持有效,下次启动只补没装完的 那些;没有进度窗口时(progress = false 或 KK_PROGRESS=0)在控制台按 Ctrl-C, 走的是同一条路。详见依赖安装中途能取消。

Q:Nuitka 报"没有 C 编译器"? 装 MinGW64 或 MSVC,或者直接用 kkpack main.py --backend pyinstaller(PyInstaller 不需要编译器,但没有 Nuitka 快、体积也更大)。

Q:--only-binary 相关的下载失败? kkpack 用 pip download --only-binary=:all: 取 wheel,只有源码包(sdist)的库会失败。 解决办法:换一个提供了 wheel 的版本,或改用 --backend pyinstaller + mode = "all" / include。

Q:编译很久 / 内存爆了? --jobs 1 或 2;include 里少放包;mode = "runtime"(默认)比 all 快得多。LTO 是默认关闭的,别打开。

Q:项目放在中文目录下,Nuitka 编译明明成功了却报 UnicodeDecodeError? ccache 是原生程序,日志按系统 ANSI 代码页写(中文 Windows 就是 GBK),而 Nuitka (2.7 以前)用 UTF-8 读这个日志,于是在收尾统计时崩: UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd7 ...。 kkpack 会在构建路径含非 ASCII 字符时自动加 --disable-ccache(Nuitka 只在启用 ccache 时才写这个日志,关掉即可),并在日志里打一行提示 —— 中文路径下只是失去 ccache 的 重复编译加速,功能与产物不受影响。

Q:目标平台和构建平台必须一致吗? 目前 yes —— wheel 是按构建时的平台/Python 版本下载的(默认 win_amd64)。在目标平台上打包,或用对应平台的机器各打一份。

Q:动态 import 的模块能被打包吗? kkpack 会用 AST 扫描你项目里所有 .py,识别 importlib.import_module("xxx") 并登记, 同时对包用 --include-package 收整棵子树。实在扫不到的,构建时会提示"动态导入未解析", 在 [bundle] include 里显式声明即可。

Q:用了 multiprocessing,报 Can't get attribute 'xxx' on <module '__main__'>? 把 worker 函数从 if __name__ == "__main__": 里挪到文件顶层(或独立模块)。 pickle 只能记录函数的引用位置,藏在守卫块里的对象子进程还原不出来。

Q:exe_name 能用中文或连字符吗? 能,My-App、我的工具 都可以。产物名只受 Windows 文件名字符限制,不受 Python 模块命名规则限制——kkpack 会用安全模块名编译,结束时再把产物改成你要的名字。 (详见 产物名与图标。)

Q:改了 exe_name 之后,之前打包的产物还在? 同名重复构建会 直接覆盖(旧产物被整个换掉),所以反复构建不会越堆越多。 但如果把 exe_name 换成了新名字,旧名字那份产物不会自动清理,建议先删掉 output_dir(默认 dist/),否则新旧两份同时存在、容易拷错。


已知限制

  • requirements.txt 必须存在且非空
  • 打包机需要能访问 PyPI 或镜像源(runtime 模式也一样);元数据解析需要一个支持 PEP 658 的源,默认用官方 PyPI 兜底([index] pypi_fallback = false 可关)
  • 跨平台构建暂不支持(需目标平台 + 目标 Python 版本)
  • 不支持 VCS / 本地路径依赖(git+https://...)
  • --windowed 下运行期无控制台输出,排查请配合 KK_TRACE=1

环境要求

项 要求
操作系统 仅 Windows(依赖 wheel 按 win_amd64 取,其它平台不做承诺)
Python 3.8 及以上
打包后端 Nuitka 需要 MSVC 或 MinGW64;PyInstaller 不需要 C 编译器
kkpack 自身 零第三方依赖

不确定环境行不行就先跑 kkpack doctor,上面这几项它一次体检完。


License

MIT。许可证原文随包分发:sdist 根目录的 LICENSE,以及 wheel 里的 .dist-info/licenses/LICENSE。

关于作者

微信公众号:Python卡皮巴拉

🌟【Python卡皮巴拉】—— 你的Python修炼秘籍,代码界的“神兽”驾到!🌟

Metadata

Release files for kkpack 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for kkpack 0.1.1
File Size Uploaded
kkpack-0.1.1.tar.gz 179.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for kkpack 0.1.1
File Interpreter ABI Platform
kkpack-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 275.6 kB

Release files / kkpack-0.1.1.tar.gz

Download URL kkpack-0.1.1.tar.gz
Size 179.9 kB
Tags Source
SHA-256 checksum
How to use checksums
f42ed781bed529976a8e664d70e7a9fe760f293cd16366a355ebfff3e3ae755e
BLAKE2b-256 checksum
How to use checksums
e0ee5300be02147f9404dd8e4071de59eace1278957d6a6d451f7820d2b5d847
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.2

Release files / kkpack-0.1.1-py3-none-any.whl

Download URL kkpack-0.1.1-py3-none-any.whl
Size 95.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
fd46687046127efbeebaabb01301c63f3919dd3392c8c30e4a64ee8d921766b6
BLAKE2b-256 checksum
How to use checksums
177050fc7db9b65d1dfe9e219921d003c8d57f8392a10b9861748341fadf34b7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.2

Release history Release notifications | RSS feed

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page