什么是“编译”?
我们需要明确这里的“编译”是什么意思,在 Python 的世界里,我们通常说的“编译”并不是指像 C++ 或 Java 那样编译成机器码,而是指将 .py 源代码文件转换成一种字节码 文件(.pyc 文件)。

这样做的主要目的不是提升运行速度(虽然字节码加载比源码快),而是:
- 隐藏源代码:将你的核心算法或商业逻辑保护起来,防止用户直接查看和修改
.py文件。 - 提升模块加载速度:Python 解释器在导入模块时,会先检查是否存在对应的
.pyc文件,如果存在且比.py文件新,就会直接加载字节码,跳过语法解析和编译的步骤,从而略微提升启动速度。 - 分发便利性:可以只分发
.pyc文件和必要的非代码文件(如.so,.dll),而不需要分发.py源码。
使用 PyInstaller 打包成可执行文件或库包 (最推荐)
这是最常用、最简单,也是最强大的方法,PyInstaller 可以将你的 Python 程序(包括所有依赖)打包成一个独立的可执行文件(在 Windows 上是 .exe,在 macOS 上是 .app,在 Linux 上是二进制文件),或者一个可以直接被 import 的库。
场景:你想把一个 my_module.py 打包,让其他项目可以直接 import my_module 来使用。
步骤 1: 安装 PyInstaller
pip install pyinstaller
步骤 2: 准备你的 Python 代码
假设你有一个核心模块 my_secret_module.py,里面包含你不想公开的函数。

my_secret_module.py
# 这是一个你想保护的模块
def secret_function(a, b):
"""这是一个核心计算函数,不想让别人看到实现细节"""
print("正在执行秘密计算...")
# 这里可能包含复杂的算法
result = a * a + b * b + 2 * a * b
return result
def public_function(x):
"""这是一个公开的函数,它调用了秘密函数"""
print("公开函数被调用了")
return secret_function(x, x)
步骤 3: 使用 PyInstaller 打包
打开终端,进入 my_secret_module.py 所在的目录,然后运行以下命令:
# -D 或 --onedir: 打包成一个文件夹(包含可执行文件和所有依赖),推荐用于库 # --name: 指定打包后的文件夹名称 # --distpath: 指定输出目录 (可选) # --specpath: 指定 .spec 文件位置 (可选) pyinstaller -D --name=my_cool_lib my_secret_module.py
命令解释:
-D或--onedir: 创建一个目录,里面包含一个可执行文件和所有需要的动态链接库等,这种方式更适合作为“库”来分发,因为它保留了文件结构,方便 Python 解释器找到模块。--name=my_cool_lib: 给打包后的文件夹起一个你想要的名字。my_secret_module.py: 这是你要打包的入口文件。
步骤 4: 查看结果和安装
运行命令后,你会看到几个新文件夹:

build/: 存放临时文件,可以删除。dist/: 这是最重要的文件夹,里面会有一个名为my_cool_lib的文件夹。my_secret_module.spec: PyInstaller 的配置文件,可以用于更复杂的打包配置。
进入 dist/my_cool_lib 目录,你会看到类似这样的结构:
dist/my_cool_lib/
├── my_cool_lib/
│ ├── __init__.py <-- Python 包的初始化文件
│ ├── my_secret_module.cpython-39.pyc <-- 编译后的字节码文件!
│ ├── ... (其他依赖的 .pyc 文件)
│ └── lib/
│ └── ... (一些动态链接库 .dll 或 .so)
└── my_cool_lib.exe <-- 一个可执行文件(Windows上)
如何安装和使用这个库?
-
安装:将
dist/my_cool_lib/my_cool_lib这个内部的文件夹复制到你项目的site-packages目录下,或者使用pip安装。# 进入 dist 目录 cd dist # 使用 pip 安装内部的文件夹 pip install ./my_cool_lib/
这会把
my_cool_lib包安装到你的 Python 环境中。 -
使用:现在你可以在任何其他 Python 项目中像使用普通库一样使用它了。
main_app.py (调用方)
import my_cool_lib # 直接导入打包后的包名
# 调用公开函数
result = my_cool_lib.public_function(10)
print(f"计算结果是: {result}")
# 注意:你无法直接导入 secret_function,因为它在 my_secret_module.py 中
# from my_cool_lib import secret_function # 这会报错,除非你显式导出
使用 py_compile 模块 (手动编译)
这种方法最直接,就是手动生成 .pyc 文件,它非常简单,但功能有限,通常用于简单的模块分发。
场景:你只想快速把几个 .py 文件编译成 .pyc,然后一起分发。
步骤 1: 编译单个文件
import py_compile
py_compile.compile('my_secret_module.py', 'my_secret_module.pyc')
这会生成一个 my_secret_module.pyc 文件。
步骤 2: 编译一个包的所有文件
如果你有一个包,可以使用 Python 的 -m compileall 命令。
# 编译当前目录下的所有 .py 文件 python -m compileall . # 编译指定目录 python -m compileall ./my_package/
如何分发和使用?
- 分发:你将
.pyc文件和你的项目其他非代码文件(如配置文件、数据文件)一起打包。 - 使用:调用方需要将这个包含
.pyc文件的目录结构放在 Python 的模块搜索路径中(site-packages),然后就可以import了。
缺点:
- 不跨版本:
.pyc文件是与 Python 版本和操作系统强相关的,在 Python 3.9 上编译的.pyc文件不能在 Python 3.10 上使用。 - 容易被反编译:
.pyc文件可以被轻易地反编译回几乎可读的 Python 代码(使用uncompyle6等工具),安全性不高。 - 不包含元数据:没有版本号、依赖信息等,不方便管理。
这种方法只适用于非常简单的、对安全性要求不高的内部工具。
使用 Cython 将 Python 代码编译成 C 扩展 (性能与安全兼顾)
如果你的目标是极致的性能和更强的代码保护,Cython 是最佳选择,Cython 允许你写一种 Python 和 C 混合的语言,然后将其编译成 C 代码,并最终编译成 Python 可以调用的动态链接库(.so 或 .pyd)。
场景:你有一个计算密集型的 Python 模块,既想让它跑得飞快,又想保护它的源码。
步骤 1: 安装 Cython 和必要的编译工具
pip install cython
你还需要安装 C 语言编译器,比如在 Windows 上是 Visual C++ Build Tools,在 Linux 上是 gcc。
步骤 2: 创建 .pyx 文件
将你的 Python 代码改成 .pyx 后缀。
my_fast_module.pyx
# 定义一个 C 类型的变量,提升性能
def fast_function(int a, int b):
"""一个用 Cython 编写的、性能极高的函数"""
cdef int result = a * a + b * b + 2 * a * b
return result
步骤 3: 创建 setup.py 文件
这个文件是 Cython 的编译配置脚本。
setup.py
from setuptools import setup
from Cython.Build import cythonize
import os
# 获取当前目录名作为模块名
module_name = os.path.basename(os.getcwd())
setup(
name=module_name,
ext_modules=cythonize("my_fast_module.pyx"),
)
步骤 4: 编译
在终端运行:
python setup.py build_ext --inplace
步骤 5: 查看结果
如果成功,你会生成一个文件,如 my_fast_module.cpython-39-x86_64-linux-gnu.so (Linux) 或 my_fast_module.cp39-win_amd64.pyd (Windows),这个文件就是编译好的 C 扩展。
如何使用?
调用方可以直接 import 这个模块,就像导入普通 Python 模块一样,他们得到的只有 .so 或 .pyd 文件,看不到你的 Python/Cython 源码。
main_app.py (调用方)
import my_fast_module
result = my_fast_module.fast_function(10, 20)
print(f"快速计算结果是: {result}")
优点:
- 性能大幅提升:对于计算密集型任务,性能可以媲美 C。
- 代码保护性极强:C 源码(
.c文件)可读性差,.so/.pyd文件更是无法直接查看逻辑。 - 生成标准 Python 扩展:与 Python 解释器无缝集成。
缺点:
- 复杂度高:需要学习 Cython 语法,并且配置编译环境。
- 有编译开销:每次修改代码后都需要重新编译。
总结与选择
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| PyInstaller | 简单易用,能处理所有依赖,跨平台,打包成标准库或单文件 | 启动速度略慢(因为是完整打包),最终文件体积较大 | 绝大多数场景,特别是当你想保护代码、方便分发,且不关心极致性能时,这是首选推荐。 |
py_compile |
最简单,无外部依赖 | 安全性低(易反编译),不跨版本,无元数据 | 简单的内部工具,或只想略微提升模块加载速度。 |
| Cython | 性能最高,代码保护性最强 | 复杂,需要C编译环境,有学习曲线 | 计算密集型任务,对性能和代码安全都有极高要求的库。 |
给你的建议:
- 如果你想快速、方便地分发你的 Python 库并隐藏源码,请使用 PyInstaller,并选择
-D(onedir) 模式。 - 如果你的库核心是大量科学计算或循环,并且你希望它跑得飞快,请考虑 Cython。
- 如果只是想临时编译几个文件,或者对安全性要求极低,可以用
py_compile。
