第10章 打包发布
33 分钟阅读
第十章:Python 代码运行与打包
本章我们将化身为"代码快递员",学会如何把 Python 代码送到用户手上——无论是自己运行、命令行调用,还是打包成.exe发给不懂代码的七大姑八大姨。
想象一下:你写了一个超棒的程序,现在想让全世界人民都能用它。问题来了——不是每个人都有 Python 环境,也不是每个人都愿意在终端里敲 python script.py。这时候,你就需要打包了。
在这一章里,我们从最基础的"怎么运行 Python 代码"开始,一路升级到"怎么把代码变成双击就运行的程序",再到"怎么把代码发布到 PyPI 让全世界 pip install 你"。
准备好,上车!
10.1 Python 代码运行方式
你知道吗,Python 代码有 N 种运行方式,比奶茶的配料表还多。让我们一种一种来看。
10.1.1 脚本文件运行
脚本文件运行,就是最传统、最常见、最"朴实无华"的方式。
10.1.1.1 python script.py
这是 Python 世界里的"Hello World"级别的操作。
| |
直接在终端输入:
| |
小知识:这里的
python其实是个程序,它负责读取你写的代码,然后一行一行解释执行。Python 解释器就是干这个的——就像翻译员,你说什么它就帮你翻译成机器能懂的语言。
10.1.1.2 Windows 双击运行
在 Windows 上,如果你想让脚本像普通软件一样双击就能跑,有几个小技巧:
方法一:创建快捷方式
- 右键点击
hello.py→ 创建快捷方式 - 右键快捷方式 → 属性
- 在"目标"栏里改成
C:\Python312\python.exe "C:\你的路径\hello.py" - 改个好看的图标,大功告成!
方法二:改文件关联(适合冒险家)
| |
方法三:加一个批处理文件
创建一个 run.bat:
| |
双击 run.bat,黑窗口一闪而过,你就能看到输出了!
小贴士:如果你想让窗口跑完后停留,加上
pause;如果想隐藏黑窗口,可以用 VBScript——但这都是雕虫小技,我们继续往下看正经方法。
10.1.2 模块运行
模块(Module)运行,这是一种更"组织化"的运行方式。想象你有一个项目,里面有多个 .py 文件,每个文件负责不同的功能。
10.1.2.1 python -m module_name
-m 的意思是"以模块方式运行"。
假设你的项目结构是这样的:
myproject/
├── __init__.py # 空文件,但告诉 Python 这是一个包
└── main.py
| |
运行它:
| |
小知识:
__name__这个变量很神奇。当文件被直接运行时,它的值是"__main__";当被作为模块导入时,它的值是模块名。这行if __name__ == "__main__":的意思是"只有直接运行这个文件时才执行",常用于写测试代码。
10.1.2.2 python -m package.module
运行包里的特定模块:
| |
这比直接 python myproject/main.py 更好,因为:
- Python 能正确找到模块(不会有路径问题)
- 包会被正确初始化(
__init__.py会先执行)
10.1.3 包运行
有时候你想直接运行一个包,而不是某个模块。比如很多 CLI 工具(命令行工具)就是这样设计的。
10.1.3.1 python -m package
假设你有一个包叫 myapp:
myapp/
├── __init__.py
└── __main__.py # 这是关键!
| |
| |
小知识:
__main__.py是个约定。当用python -m 包名运行一个包时,Python 会自动找这个包里的__main__.py并执行它。就像每个电影剧组都有一个"开场"的角色,__main__.py就是包的"开场"。
10.1.4 -c 参数(直接运行字符串代码)
有时候你只想快速测试一行代码,或者验证某个函数的结果,不想新建文件——-c 参数就是你的瑞士军刀!
| |
计算一下:
| |
批量操作也行:
| |
小贴士:
-c在写 Shell 脚本时特别有用,可以内嵌 Python 代码片段。但要注意引号嵌套,Windows 的 CMD 和 PowerShell 还有区别,小心别把自己绕晕了。
10.1.5 -i 参数(运行后进入交互模式)
-i 的意思是"交互模式"(interactive)。脚本运行完毕后,不退出 Python 解释器,而是留下一个交互式环境让你继续操作。
| |
| |
适用场景:调试的时候超级有用!你可以在脚本里设置好各种变量,然后进入交互模式慢慢测试各个函数,不用一遍遍重新运行脚本。
10.1.6 shebang 行
shebang(发音:shi-bang,不是"死棒"):也叫 hashbang,就是脚本第一行的
#!,告诉系统这个脚本应该用什么程序来执行。类 Unix 系统(Linux、macOS)专用,Windows 用户可能会困惑——但别担心,PyInstaller 会帮你搞定的。
10.1.6.1 #!/usr/bin/env python3
shebang 必须写在文件的第一行(前面不能有空行、不能有注释),而且推荐用 env 去找解释器,而不是硬编码 /usr/bin/python3:虚拟环境、Homebrew、各个发行版的解释器路径都不一样,env 会顺着 PATH 找到当前环境里真正在用的那个。
| |
shebang 的几种写法:
| |
小知识:
/usr/bin/env是个程序,它的任务是"在 PATH 环境变量里找程序"。所以#!/usr/bin/env python3的意思是:去 PATH 里找 python3,然后用它来运行这个脚本。这种方式比硬编码路径好,因为不同系统 Python 安装位置可能不一样。
10.1.6.2 chmod +x script.py 后直接运行
在 Linux/macOS 上:
| |
等等!Windows 用户别走!Windows 不认识 shebang,它看到
#!会当成普通注释,直接跳过。不过没关系,等我们学到 PyInstaller 的时候,Windows 用户也能拿到双击即用的程序。⚠️ 但这里必须澄清一个流传极广的说法:PyInstaller 不支持交叉编译。你打包好的可执行文件里带着一个完整的 Python 运行时和当前平台的二进制依赖,所以在 Windows 上打包只能得到 Windows 程序,在 Linux 上打包只能得到 Linux 程序。想同时提供多个平台的版本,就得在各自的平台上(或者用 CI 的多个 runner)分别打包。
10.1.7 python 命令行参数速查
Python 解释器有一大堆命令行参数,下面几个最常用:
10.1.7.1 -B:阻止写入 .pyc 文件
Python 运行时会生成 .pyc 文件(编译后的字节码),用来加速下次启动。加 -B 可以阻止这件事:
| |
什么?你不想看到满桌子的
__pycache__文件夹?-B就是你的清洁工!
10.1.7.2 -v:verbose,详细输出导入信息
-v 会在每次导入模块时打印一行信息,还会把模块搜索路径一并列出来。排查"为什么 import 到的是别的同名模块"“为什么包明明装了却找不到"这类问题时,这堆输出比任何猜测都直接。
| |
这会打印出每一个模块导入的详细信息。输出超级多,但超级有用——当你不知道为什么程序找不到某个模块的时候,用 -v 看看它到底在哪些路径里翻箱倒柜:
import 'sys' # <_frozen_importlib.SourceFileLoader...>
import 'os' # <_frozen_importlib.SourceFileLoader...>
# ... 一大堆 ...
10.1.7.3 -W:Warning 控制
Python 的警告(Warning)控制系统,有时候警告太多烦死人,可以用 -W 来过滤:
| |
实用场景:很多库会抛出 FutureWarning 或 DeprecationWarning,但你暂时不想管。加
-W ignore眼不见为净!
10.2 命令行参数解析
现在你的脚本可以运行了。但问题是:如果你的脚本需要接收用户输入的文件名、开关选项、数字参数呢?你总不能每次都改代码吧!
这就要说到命令行参数解析了。
10.2.1 sys.argv:最原始的参数获取
sys.argv 是最简单、最直接的方式,就像用筷子吃饭——能吃饱,但不够优雅。
10.2.1.1 sys.argv[0] 是脚本名,sys.argv[1:] 是参数
sys.argv 就是一个列表:第 0 个元素是脚本自己的名字(可能是相对路径,也可能是模块路径),真正的参数从 sys.argv[1] 开始。列表里的每个元素都是字符串,数字要自己转。
| |
运行:
| |
输出:
脚本自己叫:greet.py
参数列表是:['Alice', '42', 'Hello World']
第一个参数是:Alice
argv[0] = greet.py
argv[1] = Alice
argv[2] = 42
argv[3] = Hello World
小问题:
sys.argv的返回值都是字符串!所以42你拿到手是'42',不是42,需要自己转换类型。
| |
缺点:没有帮助信息,没有类型检查,没有默认值,没有
-h自动生成使用说明。用sys.argv就是自己手工切菜——可以,但不是大厨的做法。
10.2.2 argparse:标准库完整参数解析
argparse 是 Python 标准库,专门用来处理命令行参数。用它你可以轻松做出专业的 CLI 工具——带帮助信息、类型检查、默认值、子命令,应有尽有。
10.2.2.1 基础用法
argparse 的套路非常固定:建一个 ArgumentParser(顺手写上 description,--help 会好看很多)、用 add_argument 逐个声明参数、最后 parse_args() 取结果。-h/--help 是自动送的,不用自己写。
| |
运行看看:
| |
usage: basic_argparse.py [-h] [--age AGE] [-v] name
这是一个演示程序
positional arguments:
name 你的名字
options:
-h, --help 显示帮助信息
--age AGE 你的年龄(默认18)
-v, --verbose 显示详细信息
| |
你好,小明!
你20岁了。
(这是详细信息:程序运行成功!)
小知识:
--help是自动生成的!argparse会根据你的参数定义自动生成使用说明和帮助文档,不用你手动写。
10.2.2.2 add_argument 参数详解
add_argument 是 argparse 的核心,参数超级多:
| 参数 | 说明 | 示例 |
|---|---|---|
name | 位置参数名 | parser.add_argument("file") |
--name | 可选参数 | parser.add_argument("--input", "-i") |
type | 参数类型 | type=int, type=float, type=open |
default | 默认值 | default=0 |
help | 帮助说明 | help="输入文件路径" |
required | 是否必填 | required=True |
choices | 限定选项 | choices=["A", "B", "C"] |
action | 动作 | store_true(开关) |
nargs | 参数个数 | nargs="*"(任意多个),nargs="?"(0或1个),nargs="+"(至少1个) |
| |
| |
主文件:data.txt
重复次数:5
难度:hard
安静模式:False
其他文件:['file2.txt', 'file3.txt']
10.2.2.3 互斥组配置
有些参数不能同时使用,比如"显示版本"和"输入模式”——这就叫互斥组(Mutually Exclusive Group)。
| |
| |
error: argument -q/--quiet: not allowed with argument -v/--verbose
10.2.2.4 子命令配置
大项目通常有子命令,像 git commit、git push、git pull 那样。argparse 也支持!
| |
| |
usage: file_tool.py [-h] {compress,extract} ...
positional arguments:
{compress,extract} 子命令
compress 压缩文件
extract 解压文件
10.2.3 click:命令行界面构建框架
argparse 很强大,但写起来还是有点繁琐。click 是一个更优雅的 CLI 构建库,它用装饰器(Decorator)让代码更简洁、更易读。
10.2.3.1 安装
click 就是个纯 Python 包,pip install click 即可。它和 argparse 的关系不是替代而是"更顺手":功能集合相近,但写法从"配置对象"变成了装饰器。
| |
10.2.3.2 @click.command() 和 @click.option()
@click.command() 把一个函数变成命令,@click.option() 声明选项:参数名默认由长选项名推导(--count → 函数参数 count),类型、默认值、帮助文字都写在装饰器里。函数签名本身就成了参数文档。
| |
| |
Usage: click_demo.py [OPTIONS]
这是一个演示 Click 的简单程序
Options:
-n, --name TEXT 打招呼的对象 [default: 世界]
-c, --count INTEGER 重复次数 [default: 1]
--help 显示帮助
| |
你好,小明!
你好,小明!
你好,小明!
小知识:为什么要用
click.echo而不是click.echo会自动处理不同平台的换行符。
10.2.3.3 参数类型
click 支持多种参数类型:
| |
| |
文件:test.txt
数字:42 (类型:int)
比例:3.14
模式:A
10.2.3.4 命令组
和 argparse 的子命令类似,click 也有命令组:
| |
| |
Usage: click_commands.py [OPTIONS] COMMAND [ARGS]...
文件处理工具集
Commands:
compress 压缩文件
extract 解压文件
info 查看文件信息
| |
10.2.4 Typer(Click 进阶版,FastAPI 团队出品)
Typer 是基于 Click 开发的,语法更简洁,特别适合已经熟悉 Python 类型提示的人。
10.2.4.1 安装
Typer 同样是纯 Python 包,pip install typer 就能用;如果还想要更漂亮的帮助输出和 shell 自动补全,再装上 rich 和 shellingham 即可。
| |
10.2.4.2 @app.command()
Typer 的写法几乎是"加个类型提示就完事":参数类型、默认值、布尔开关全部从函数签名推导出来(count: int = 1 直接变成 --count)。app.command() 把函数注册成子命令,不指定名字就默认用函数名。
| |
| |
Usage: typer_demo.py [OPTIONS] NAME [TIMES]
打招呼程序
Arguments:
name [default: 世界]
times [default: 1]
Options:
--help 显示帮助
| |
你好,小明!
你好,小明!
你好,小明!
小知识:Typer 利用了 Python 3.6+ 的类型提示(Type Hints)功能。参数类型直接写在函数签名里,不用额外配置,Typer 自动帮你生成 CLI 参数类型。代码即配置,简洁到飞起!
10.2.4.3 自动生成 CLI 文档
Typer 能自动生成 Bash 和 Fish 的自动补全脚本:
| |
| |
这个功能对于写给团队使用的工具来说特别有用——有了自动补全,用户体验直接提升一个档次!
10.2.5 fire:自动生成 CLI
Google 出品的 fire 更激进——它可以自动把任何 Python 程序、函数、类生成 CLI,完全零配置!
10.2.5.1 安装
Python Fire 是 Google 的开源项目,pip install fire 一条命令装好。除了自动生成 CLI,它还能把任意 Python 对象(类、模块、字典)导出成命令行接口。
| |
10.2.5.2 任何 Python 函数自动生成 CLI
fire.Fire(函数) 会把函数的参数直接映射成命令行参数,类型靠默认值和 help 注解推断。它适合把脚本"临时变成命令",但要做正式的 CLI,参数校验和帮助信息的精细度仍然不如 click / Typer。
| |
| |
你好,小明!(第1次)
你好,小明!(第2次)
你好,小明!(第3次)
| |
| |
| |
fire 的哲学:最少的代码,最大的效果。你不需要额外写任何 CLI 代码,只要导入 fire,调用
fire.fire(),它就会自动把你定义的所有函数、类生成命令行工具。懒人必备!
10.3 打包为可执行文件
好了,现在你能熟练运行和配置 Python 代码了。但问题是——你的用户可能连 Python 都没安装。总不能让大妈运行 pip install numpy 吧?
这时候就需要把 Python 代码打包成可执行文件(.exe),让任何人都能双击运行!
10.3.1 PyInstaller:跨平台打包
PyInstaller 是最流行的 Python 打包工具,能把 Python 程序打包成单个可执行文件或整个目录。
10.3.1.1 安装
装 PyInstaller 时建议放进项目自己的虚拟环境:它要分析你环境里装了哪些包,装在全局环境容易把不相干的包一并打进去,产物会莫名其妙地变大。
| |
10.3.1.2 基本打包命令
先写一个简单的程序:
| |
10.3.1.2.1 –onefile 单文件模式
打包成单个文件:
| |
打包完成后,dist/ 目录下会出现一个 hello_packaged.exe(Windows)或 hello_packaged(Linux/Mac)。
优点:只有一个文件,方便分发。 缺点:启动较慢,因为每次运行都要先解压。
10.3.1.2.2 –onedir 目录模式
打包成整个目录:
| |
dist/hello_packaged/ 目录下会有:
- 主程序(
hello_packaged.exe) - 依赖的 DLL 和资源文件
- Python 运行时
优点:启动快。 缺点:文件多,散落一地。
10.3.1.3 图标配置
给程序加个图标,看起来更专业!
| |
图标格式要求:
- Windows:
.ico格式- macOS:
.icns格式- Linux:可执行文件本身没有统一的图标机制,
--icon在 Linux 上不起作用(桌面环境的图标来自.desktop文件);如果给的是 PNG 这类非平台格式的图片,PyInstaller 会尝试调用 Pillow 帮你转成.ico/.icns
没有图标?可以去 iconifier.net 或 favicon.io 之类的网站生成。
10.3.1.4 隐藏依赖配置
有些库(比如 torch、tensorflow、cv2)在导入时非常隐蔽,PyInstaller 看不到它们的依赖关系。这时候需要手动指定。
| |
多个隐藏依赖:
| |
10.3.1.5 spec 文件详解
PyInstaller 的配置都存在 .spec 文件里。第一次打包会自动生成,之后你可以手动编辑它。
| |
打包命令:
| |
10.3.1.5.1 Analysis、PYZ、EXE、COLLECT 阶段
PyInstaller 打包分四个阶段:
flowchart LR
A["源文件<br/>hello.py"] --> B["Analysis<br/>分析阶段"]
B --> C["PYZ<br/>压缩阶段"]
C --> D["EXE<br/>生成可执行文件"]
D --> E["COLLECT<br/>收集资源"]
E --> F["dist/<br/>最终产物"]| 阶段 | 类 | 作用 |
|---|---|---|
| Analysis | Analysis | 分析脚本,递归找出所有导入的模块 |
| PYZ | PYZ | 把所有 Python 模块压缩成 .pz 格式 |
| EXE | EXE | 生成最终的可执行文件 |
| COLLECT | COLLECT | 收集资源文件(图片、配置文件等) |
10.3.1.5.2 datas 配置
如果你的程序需要读取外部文件(图片、配置、音频等),需要用 datas 配置把它们打包进去:
| |
运行时读取:
| |
10.3.1.5.3 hiddenimports 配置
当代码使用 importlib、plugins、__getattr__ 等动态导入时,PyInstaller 可能会漏掉某些依赖,需要手动声明:
| |
10.3.1.6 常见问题与解决
问题一:打包后运行报错 “Failed to execute script”
| |
常见原因:
- 缺少
--hidden-import(漏掉了动态导入的模块) datas配置不对(找不到资源文件)- 编码问题(Windows 控制台默认 GBK)
问题二:打包后文件太大
| |
或者用虚拟环境只安装需要的包:
| |
问题三:Windows 下打包有黑色控制台窗口
GUI 程序不需要黑窗口:
| |
问题四:杀毒软件报毒
打包后的 .exe 是从零开始生成的,杀毒软件不认识它,就可能报警。这是正常现象(尤其是加了 UPX 压缩的)。
解决:
- 申请代码签名证书,给 exe 签名
- 提交给杀毒软件厂商白名单(如 Microsoft SmartScreen)
- 使用开源打包工具(PyInstaller 本身是开源的,可以审查代码)
10.3.2 Nuitka:将 Python 编译为 C 再编译为可执行文件
PyInstaller 是"打包",Nuitka 是"编译"。Nuitka 把 Python 代码编译成 C 代码,再用 C 编译器编译成机器码——性能更高!
10.3.2.1 安装
Nuitka 走的是"把 Python 编译成 C、再编译成二进制"的路线,pip install nuitka 会顺带装好它需要的辅助库;第一次编译还要求本机有 C 编译器(gcc / clang / MSVC)。
| |
10.3.2.2 基本命令
Nuitka 最常见的组合是 --standalone(连同依赖打包成一个目录)加 --onefile(再压成单个文件)。和 PyInstaller 一样,它不跨平台:在哪个系统上跑,就只能产出那个系统的可执行文件。
| |
10.3.2.3 性能优化选项
--optimize=3 让 Nuitka 在编译期做更激进的优化,--lto 打开链接期优化,--remove-output 则在打包完成后清掉中间文件。优化开得越猛,编译时间越长——这是必然的取舍。
| |
10.3.2.4 C 编译器选择
Nuitka 需要 C 编译器:
| 系统 | 编译器 |
|---|---|
| Windows | MinGW64(会自动下载)或 MSVC |
| Linux | gcc |
| macOS | clang |
| |
10.3.2.5 Nuitka vs PyInstaller vs cx_Freeze 对比
| 特性 | PyInstaller | Nuitka | cx_Freeze |
|---|---|---|---|
| 原理 | 打包字节码 | 编译成 C | 打包字节码 |
| 性能 | 解释执行 | 编译执行 | 解释执行 |
| 体积 | 较大 | 中等 | 中等 |
| 启动速度 | 慢(单文件需解压) | 快 | 中等 |
| 代码保密 | 差(.pyz 可解压) | 好(编译后难以反编译) | 差 |
| 依赖管理 | 手动配置 | 自动检测 | 需配置 |
| 学习曲线 | 低 | 中 | 低 |
| 适用场景 | 快速分发 | 追求性能/保密 | 跨平台分发 |
选择建议:
- 快速分享给不懂技术的用户 → PyInstaller
- 追求运行性能或代码保密 → Nuitka
- 需要跨平台(Windows/Mac/Linux)统一打包 → cx_Freeze 或 PyInstaller
10.3.3 cx_Freeze:跨平台打包
cx_Freeze 又是一个打包工具,语法和 PyInstaller 类似。
10.3.3.1 安装
pip install cx_Freeze。它和 PyInstaller 的核心区别是"配置驱动":打包规则写在 setup.py 里,更适合和 setuptools 的构建流程配合使用。
| |
10.3.3.2 setup.py 配置
setup() 里的 executables 列表描述"要生成哪些可执行文件",build_exe_options 则负责打包细节(额外模块、要包含的数据文件等)。改完配置执行 python setup.py build,产物会出现在 build/ 目录下。
| |
打包:
| |
输出在 build/ 目录下。
10.3.4 briefcase:BeeWare 项目打包工具
briefcase 是 BeeWare 项目的一部分,主要用于打包 Python 应用到各个平台。
10.3.4.1 安装
Briefcase 属于 BeeWare 生态,pip install briefcase 之后用 briefcase create、briefcase build、briefcase run 这一串子命令,把你的 Python 代码变成各平台上的原生安装包。
| |
10.3.4.2 支持平台
| 平台 | 输出格式 |
|---|---|
| Windows | .msi 安装包 / .exe |
| macOS | .app 应用 / .dmg 安装包 |
| Linux | .deb, .rpm, .appimage |
| iOS | |
| Android | APK(通过 Python 标准库) |
| |
briefcase 的特点是生成的应用程序看起来和原生应用一样(有自己的窗口、图标、菜单),而不是带黑窗口的控制台程序。如果你做的是 GUI 应用,briefcase 是很好的选择。
10.4 Python 包发布
现在你已经会打包了,接下来是——把包发布到 PyPI,让全世界都能 pip install 你的包!
PyPI(Python Package Index):Python 官方的第三方包仓库,类似 npm 之于 JavaScript,crates.io 之于 Rust。全世界的人都在这里分享自己的 Python 包。
10.4.1 setup.py / setup.cfg 详解
setup.py 是 Python 打包的"老前辈",虽然新标准推荐用 pyproject.toml,但理解 setup.py 对了解打包历史很有帮助。
10.4.1.1 setup() 函数参数详解
setup() 的每个参数都对应一段元数据:name/version 是包名和版本,packages=find_packages() 自动收集所有子包,install_requires 声明运行依赖,entry_points 定义命令行入口。这套写法至今仍然有效,但新项目更应该把它搬进 pyproject.toml。
| |
10.4.1.2 find_packages() 用法
find_packages() 会自动在当前目录下找所有包含 __init__.py 的目录,当作包:
mypackage/
├── __init__.py
├── module1.py
├── subpackage/
│ ├── __init__.py
│ └── module2.py
└── tests/
├── __init__.py
└── test_module1.py
| |
10.4.1.3 install_requires 依赖声明
install_requires 声明的是运行时依赖,用户在 pip install 时会自动安装:
| |
可选依赖(用户需要时额外安装):
| |
用户安装方式:
| |
10.4.2 pyproject.toml:现代打包标准(PEP 517/518/621)
pyproject.toml 是现代 Python 打包的标准格式,取代了老旧的 setup.py。所有新项目都应该用它!
10.4.2.1 [build-system] 构建系统配置
告诉 pip 使用什么工具来构建这个包:
| |
10.4.2.2 [project] 项目元数据配置
[project] 是 PEP 621 定义的现代元数据表:名字、版本、依赖、Python 版本要求全放在这里,setuptools、hatch、flit、pdm 这些构建后端都认这套格式。写了它,就不必再维护一份信息重复的 setup.py。
| |
10.4.2.3 [project.optional-dependencies] 可选依赖
[project.optional-dependencies] 用来声明"可选的额外依赖组":用户执行 pip install mypackage[dev],就会把 dev 组里列的包一起装上。把测试、文档、开发工具分门别类放进去,普通用户的安装才能保持轻量。
| |
10.4.2.4 [project.scripts] 命令行入口
这是非常强大的功能——定义命令行入口,安装后用户可以直接在终端调用:
| |
| |
安装这个包后,用户就能直接运行 myapp 和 greet 命令了!
完整的 pyproject.toml 示例:
| |
10.4.3 build 工具:打包
10.4.3.1 安装
pip install build。这个工具的职责很单纯:调用 pyproject.toml 里声明的构建后端,生成 sdist 和 wheel。它是官方推荐的构建入口,取代了过去直接在项目里执行 setup.py 的做法。
| |
10.4.3.2 python -m build 生成 dist/ 目录
python -m build 默认先打一个源码包(sdist),再在隔离环境里用它构建出 wheel。产物都在 dist/ 下:.tar.gz 是源码分发,.whl 才是用户实际安装的东西。只想生成 wheel 就加 --wheel。
| |
构建完成后,dist/ 目录下会有:
dist/
├── mypackage-1.0.0-py3-none-any.whl # wheel 包(pip install 用这个)
└── mypackage-1.0.0.tar.gz # 源码包
10.4.4 twine:安全上传到 PyPI
为什么不用 python setup.py upload?因为它是明文上传密码,极其不安全。twine 使用 HTTPS,更安全!
10.4.4.1 安装
pip install twine。twine 只做一件事:把构建产物安全地上传到 PyPI——全程 HTTPS,支持 API Token,还能在上传前校验元数据格式。
| |
10.4.4.2 twine upload 上传命令
twine upload dist/* 会把 dist/ 里的所有产物推到 PyPI,加 --repository testpypi 则推到测试站点。要注意:同名版本号不能重复上传,PyPI 不允许覆盖已发布的版本,这是它的硬性规则。
| |
10.4.4.3 PyPI 账号和 API Token 配置
第一步:去 PyPI.org 注册账号
访问 https://pypi.org/account/register/,填写用户名、密码、邮箱。
第二步:生成 API Token
- 登录 PyPI
- 进入 Account Settings → API tokens
- 点击 “Add API token”
- 复制生成的 token(格式:
pypi-xxxxxxxxxxxx)
第三步:配置 ~/.pypirc
在用户目录下创建或编辑 .pypirc 文件(Windows 是 C:\Users\你的用户名\):
| |
小贴士:
username = __token__不是让你填用户名,而是字面量__token__,密码才是你的 token。Token 本身就是密码的意思,这样设计更安全。
10.4.5 版本号管理
版本号不是随便写的,有一套标准叫 语义化版本(Semantic Versioning)。
10.4.5.1 Semantic Versioning(语义化版本)
格式:主版本.次版本.修订号
1.0.0
↑ ↑ ↑
| | └── 补丁版本:修复 bug,小改动
| └──── 次版本:新增功能(向后兼容)
└────── 主版本:不兼容的重大改动
| 场景 | 例子 |
|---|---|
| 首次发布 | 1.0.0 |
| 修复 bug | 1.0.1 |
| 新功能(向后兼容) | 1.1.0 |
| 破坏性更新 | 2.0.0 |
10.4.5.2 Alpha / Beta / RC 版本标注
正式发布前,会有几个测试阶段:
| 阶段 | 标注 | 说明 |
|---|---|---|
| Alpha | 1.0.0a1 或 1.0.0a | 内部测试版,很不稳定 |
| Beta | 1.0.0b1 或 1.0.0b | 公开测试,功能差不多了 |
| Release Candidate | 1.0.0rc1 | 候选发布,大概率就是正式版了 |
| 正式版 | 1.0.0 | 稳定版 |
| |
10.4.6 发布到 Test PyPI
正式发布前,先去 Test PyPI 练练手,避免污染真实的 PyPI。
10.4.6.1 –repository testpypi 配置
Test PyPI 是一个完全独立的站点:账号、密码、项目名都和正式 PyPI 分开。先用它把流程跑通——确认元数据、README 渲染、pip install 都没问题——再往正式 PyPI 上传。
| |
10.4.6.2 测试安装方式
从 Test PyPI 安装时必须显式指定 --index-url;如果项目的依赖都在正式 PyPI 上,还要补一个 --extra-index-url,让 pip 在两个源里都能查找。
| |
10.4.7 自动发布
每次发布都要手动 twine upload?太累了!用 GitHub Actions 自动发布!
10.4.7.1 GitHub Actions + PyPI 配置
在 GitHub 仓库里创建 .github/workflows/release.yml:
| |
然后:
- 在 GitHub 仓库 → Settings → Secrets → Actions,添加
PYPI_API_TOKEN(值为 PyPI 的 API Token) - 推送一个 tag:
| |
GitHub Actions 会自动构建并发布到 PyPI!
10.5 分发依赖与环境
代码打包发布了,但如果别人想开发你的项目呢?或者你想在新电脑上继续开发?这就需要正确地分发和管理依赖。
10.5.1 requirements.txt 正确用法
requirements.txt 是 Python 项目最常用的依赖声明文件。
| |
安装:
| |
格式说明:
package— 任意版本package==1.0.0— 精确版本package>=1.0.0— 最低版本要求package~=1.4.0— 兼容版本(>=1.4.0, <1.5.0)package[extra]==1.0.0— 带可选依赖
常见问题:requirements.txt 放在项目根目录:
myproject/
├── requirements.txt # 在这里
├── README.md
├── pyproject.toml
└── src/
└── mypackage/
10.5.2 pip freeze vs poetry export
pip freeze 把当前环境里所有包的精确版本导出:
| |
生成的格式:
certifi==2023.7.22
charset-normalizer==3.2.0
idna==3.4
numpy==1.24.3
requests==2.31.0
urllib3==2.0.4
优点:精确重现环境。 缺点:包含所有 transitive dependencies(传递依赖),比如
certifi、idna这些你其实不需要关心的小包。
poetry export 是 Poetry 用户的专属,能导出兼容格式:
| |
可以排除开发依赖:
| |
10.5.3 conda 环境导出与重建
如果你用的是 conda(Anaconda/Miniconda),依赖管理方式稍有不同:
导出环境:
| |
生成的 environment.yml:
| |
重建环境:
| |
或者从零开始创建:
| |
小贴士:conda 和 pip 可以混合使用。conda 负责 conda 自己的包(编译好的二进制包),pip 负责 pip 包(纯 Python 包)。但要注意冲突检测——两边都装了同一个包的不同版本可能会出问题。
本章小结
核心要点回顾
Python 代码运行方式
python script.py是最基础的运行方式python -m module以模块方式运行python -m package直接运行包(需要__main__.py)-c参数可以直接运行字符串代码-i参数运行后进入交互模式- shebang 行 (
#!/usr/bin/env python3) 让脚本可以直接执行
命令行参数解析
sys.argv是最原始的方式,需要自己处理一切argparse是标准库,功能完整,适合复杂 CLIclick用装饰器简化 CLI 开发,用户体验好typer基于 click,类型提示友好fire自动生成 CLI,零配置,适合原型开发
打包为可执行文件
- PyInstaller:最流行,一行命令搞定,适合大多数场景
- Nuitka:编译成 C,性能更好,代码更保密
- cx_Freeze:跨平台,配置灵活
- briefcase:BeeWare 生态,原生应用体验
- 打包流程:
pyinstaller --onefile script.py→dist/下找 exe
Python 包发布
setup.py是老方式,pyproject.toml是现代标准build工具生成 wheel 和源码包twine安全上传到 PyPI- API Token 比密码更安全
- Test PyPI 用来测试发布流程
- GitHub Actions 可以自动化发布
分发依赖与环境
requirements.txt是最通用的依赖声明方式pip freeze导出精确版本poetry export可以更智能地导出- conda 用
environment.yml管理环境
实战路线图
把这一章的内容串起来,就是一条完整的"从代码到用户"流水线:先把脚本写好并加上命令行参数,再决定交付方式——是让用户自己装 Python 后 pip install,还是打成双击就能跑的可执行文件,最后才谈发布到 PyPI。
写代码 → 命令行参数 → 打包 exe → 发布到 PyPI
↓ ↓ ↓
argparse/click PyInstaller twine upload
记住:能运行、能打包、能发布,才是一个完整的 Python 项目闭环。你现在拥有了把代码送到全世界手上的能力!
下一步建议:选一个你自己的小项目,尝试用 PyInstaller 打包成 exe,或者发布到 Test PyPI 试试水。实战出真知!