Python 虚拟环境 + 嵌入式 + 编译pyd 部署方案
- 开发阶段 - 直接发源码版本
- 1. 在虚拟环境下开发 Python 项目
- 部署阶段
- 1. 创建项目文件夹
- 2. 准备嵌入器 Python 解释器
- 3. 处理第三方库
- 4. 修改 ._pth 文件添加 Python 运行环境
- 5. 添加启动 bat 脚本
- 目录结构
- 开发阶段 - 编译 pyd 再发布版本
- 安装 Cythonize
- 1. pip 安装 Cython
- 2. C编译环境
- 使用 Cythonize
- 创建 bat 批量编译
- 清理编译垃圾
- 参考资料
开发阶段 - 直接发源码版本
1. 在虚拟环境下开发 Python 项目
在开发电脑上,我们使用虚拟环境来开发我们的Python项目。
目的就是把所有此项目所需的第三方库,安装在一个单独的虚拟环境中。
方便一波带走。
部署阶段
接来下我们来到需要部署的客户电脑,以嵌入式Python解释进行我们的代码。
这样适应于一些不能安装Python的情况。
以下步骤适应离线部署,因为我们所有东西都是打包直接复制过去的,不需要通过网络再获取其它东西。
当然我这个项目比较简单,如果用了一个刁钻的依赖,可能还要在此基础上,单独处理一下。
1. 创建项目文件夹
先创建个空文件 my_project
来放整个项目:(当然名称随意)
- Python解释器
- 项目代码
- 启动bat文件
- 其它相关文件
2. 准备嵌入器 Python 解释器
- 解压 python-3.11.5-embed-amd64.zip 为
python-3.11.5-embed-amd64
放到my_project
目录 - 嵌入式Python的
版本
最好和我们开发环境的版本保持一至。 - 我偷懒不改名了,大家可以随意改成
python-3.11.5
,py3115-embed
。。。
3. 处理第三方库
将开发机虚拟环境
下的 site-packages
目录复制到 my_project
目录中
3.1. venv\Lib\site-packages
中就是虚拟环境下安装的第三方库
4. 修改 ._pth 文件添加 Python 运行环境
修改 python-3.11.5-embed-amd64
目录下的 python311._pth
文件内容,加入一些所需路径。
311
对应 python 的版本,版本不同文件名会不一样。
- 修改前
python311.zip .# Uncomment to run site.main() automatically #import site
- 修改后
注意: 这里只要这里相对路径指对就行了。可以按自己需要调整目录解构。python311.zip . # 这是 my_project .. # 这里面是我们项目代码 ..\src # 这是虚拟环境下的第三方库 ..\site-packages # 这里去掉注释 import site
5. 添加启动 bat 脚本
@echo off
chcp 65001
python-3.11.5-embed-amd64\python.exe src\main.py
pause
- 在
my_project
创建批处理run.bat
用嵌入式的python-3.11.5-embed-amd64\python.exe
来执行 py chcp 65001
是使用utf-8
编码,解决批处理中文乱码问题。
目录结构
my_project 根目录
│ run.bat 启动脚本
│
├─python-3.11.5-embed-amd64 # 嵌入式 Python 解释器
│ 嵌入式 Python 相关文件
│
├─site-packages # 虚拟环境下安装好的第三方库目录
│ └─ 虚拟环境下安装好的第三方库文件
│
└─src│ main.py│ __init__.py│ └─package # 自定义包│ __init__.py├─service│ __init__.py│ └─ 业务代码│ └─utils│ __init__.py└─ 工具类
开发阶段 - 编译 pyd 再发布版本
上面我们直接将 src
目录复制到客户电脑去跑。但有时可能不方便直接给源码,需要稍微处理一下。
这时可以用 cythonize
将 .py
编译成 .pyd
。
- 使用时 整个
src
目录解构保持不变,只是把py
全换成pyd
。 sql_util.py
编译成pyd
后名字类似这样sql_util.cp311-win_amd64.pyd
这不影响导入,直接用就行。
(我们严格遵循解释器使用相同版本的原则,所以不用改成sql_util.pyd
)
安装 Cythonize
1. pip 安装 Cython
Cythonize
是 Cython
的一个组件,安装 Cython
会自动安装 cythonize
工具。
(时刻牢记,我们要在为当前项目创建的虚拟环境下安装,免得污染全局)
pip install Cython
2. C编译环境
Cythonize
需要 C编译环境
。
gcc -v
查看有没有。我这里之前装了。没装的同学按这个来:MSYS2 安装 gcc、make 很简单。
(venv) D:\my_project>gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=D:/msys64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/13.2.0/lto-wrapper.exe
Target: x86_64-w64-mingw32
Configured with: ../gcc-13.2.0/configure --prefix=/mingw64 --with-local-prefix=/mingw64/local --build=x86_64-w64-mingw32 --host=x86_64-w64-mingw32 --target=x86_64-w64-mingw32 --with-native-system-header-dir=/mingw64/include --libexecdir=/mingw64/lib --enable-bootstrap --enable-checking=release --with-arch=nocona --with-tune=generic --enable-languages=c,lto,c++,fortran,ada,objc,obj-c++,jit --enable-shared --enable-static --enable-libatomic --enable-threads=posix --enable-graphite --enable-fully-dynamic-string --enable-libstdcxx-filesystem-ts --enable-libstdcxx-time --disable-libstdcxx-pch --enable-lto --enable-libgomp --disable-libssp --disable-multilib --disable-rpath --disable-win32-registry --disable-nls --disable-werror --disable-symvers --with-libiconv --with-system-zlib --with-gmp=/mingw64 --with-mpfr=/mingw64 --with-mpc=/mingw64 --with-isl=/mingw64 --with-pkgversion='Rev3, Built by MSYS2 project' --with-bugurl=https://github.com/msys2/MINGW-packages/issues --with-gnu-as --with-gnu-ld --disable-libstdcxx-debug --with-boot-ldflags=-static-libstdc++ --with-stage1-ldflags=-static-libstdc++
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 13.2.0 (Rev3, Built by MSYS2 project)
使用 Cythonize
- 编译当前目录下指定文件
main.py
,sql_util.py
(可以看到它是支持多个文件的)cythonize -i main.py sql_util.py
- 编译当前目录下所有
.py
但是排除__init__.py
cythonize -i *.py --exclude "__init__.py"
结果会生成到 my_project\build
目录中。
创建 bat 批量编译
在 my_project
下创建 编译PYD.bat
它将自动遍历 src
编译所有 .py
但是我排除了入口脚本 main.py
和 __init__.py
如果你有需要 __init__.py
也可以不排除。
注意:最终我们的入口脚本 main.py
还是保持源码方式用来执行。.pyd
只能作为包导入,不能直接执行。
@echo off
chcp 65001>nul
rem 先激活虚拟环境
call venv\Scripts\activate.batecho ----------------- py >>> pyd 编译开始 -----------------
setlocal enabledelayedexpansionrem 设置起始目录,当前目录下的 src
set "startDir=src"rem 根目录 src 在循环外单独处理
pushd "%startDir%"
echo 开始编译目录: "%cd%"
cythonize -i *.py --exclude "__init__.py"
popdrem 使用for /d /r循环遍历所有子目录
for /d /r "%startDir%" %%i in (*) do (rem 检查目录名是否为__pycache__if /i "%%~ni" NEQ "__pycache__" (rem 不是__pycache__,则进入并打印目录名pushd "%%i"echo 开始编译目录: "%%i"rem 使用 --exclude 排除文件,此参数可以多次使用cythonize -i *.py --exclude "main.py" --exclude "__init__.py" popd)
)
endlocal
echo ----------------- py >>> pyd 编译完成 -----------------
pause
清理编译垃圾
调试过程中我们肯定会反复编译,下面这个脚本用于清理编译生成的文件:.c
, .pyd
是的你没看错 .pyd
也一起删。
因为上面编译时它会在 my_project
通过成一个 build
文件夹,编译结果会按 src
中的目录解构存放。
所以在我们源码目录 src
下生成的这些,我们是不需要的。
@echo off
chcp 65001 >nul
echo ----------------- 开始清理 -----------------
setlocal enabledelayedexpansionrem 设置起始目录
set "startDir=src"rem 递归删除所有.c和.pyd文件
for /r "%startDir%" %%f in (*.c *.pyd) do (del "%%f"echo 删除: %%f
)endlocal
echo ----------------- 清理完成 -----------------
pause
my_project 根目录
│ 编译PYD.bat
│ 清理PYD.bat
└─build│ 嵌入式 Python 相关文件│ └─lib.win-amd64-cpython-311│ └─src │ 这下面就和 src 目录结果一样了
参考资料
Python Releases for Windows
笑虾:C++ 开发 + VSCode 调试