1.前言

这几天写了个基于flask、waitress的人脸识别服务器程序,在反复折腾之后,终于找到一个还相对简洁的部署方式。
简单来说就是使用nuitka来进行自己源码的编译,再将编译出来的exe拿到新电脑上去使用,要求新电脑有个和本机一样的虚拟环境(python版本一致,所需的库也都安装)。

一开始时,是想着直接打包发布一个完整的,带有任何所需的dll、配置文件等等资源的集合。但是尝试了好多种办法,发现要么就是打包出来的东西缺胳膊少腿,要么就是编译打包耗时极其久,反正就是不合心意。
后来发现了假如用最简单的方式使用,nuitka可以只打包编译py文件,而且速度极快。这正是我想要的。
初次实现时,我觉得这也好像不够方便,不能让打包好的程序开箱即用,不够优雅。但是转念一想,conda不是可以将环境打包吗。conda + nuitka,虽然不是一个包,而是两个包,感觉也够优雅了。而且nuitka的打包是进行二进制编译,相当于将我们的python文件给加密了,这就更加完美了。
总的来说,就目前而言,我对这种方式还是比较满意的。

2.本地开发

在本地开发时,最好用conda创建一个虚拟环境,在编程调试时,都是使用这个环境。这个环境所使用的Python要记好,然后安装的第三方包也最好记下来到requirements.txt中,方便后面到生产环境部署。或者你也可以选择不记,到时整个虚拟环境打包,然后导入到生产环境。这两种方式都是可以的。
假如想达到自己的py文件都编译,且能在生产环境运行,则必须按照下面这个架构布置文件:项目根目录下只放一个主py文件,此文件为你的程序入口文件;然后其余此py文件所使用到的其它你自己写的py文件,都要放到n个子文件夹中(子文件夹里面可以放更多的子文件、文件夹,对层级没有限制)。也就是1+n结构(1个文件+n个文件夹)
在这里插入图片描述

编写好自己的程序后,使用这个语句打包,其中app.py是你的主文件,subModule是你其它的py文件

nuitka --include-package=subModule app.py

假如有多个文件夹、模块

nuitka --include-package=subModule --include-package=subModule1 app.py

执行编译
在这里插入图片描述

假如你本地的vs编译工具太旧,可能会导致nuitka报错,说vc编译工具版本太低。这时,你需要安装一下最新的编译工具(只安装这个工具,不用安装vs)。
貌似最低得使用vs2022以上的构建工具,构建工具到这里下载
https://visualstudio.microsoft.com/zh-hans/visual-cpp-build-tools/

等编译完成后,会出现app.cmd、app.exe两个文件(具体名称视你的主文件而定)。
在这里插入图片描述
在本机上,直接双击app.cmd就可以正常启动了。
然后拷贝这两个东西到生产环境。

3.生产环境部署

首先用conda创建一个虚拟环境,名称不必和开发机的相同。
然后这个环境的初始化,可以选择导入从开发机导出的环境,也可以选择自行安装requirements.txt中的包,看你觉得哪个方便了。
假如你把前面说的app.cmd、app.exe拷贝到了新环境,直接双击启动,其实也是起不来的,哪怕你把app.cmd里面的路径改成了你生产环境的路径,大概率也是不行,因为还有一些隐藏的路径变量没有设置好。因此,我们得根据原来的脚本,改造一下来启动,注意,里面的BASE_PATH改成你部署好的conda环境

@echo off
rem 修复版本 - 覆盖Python的硬编码路径
setlocal enabledelayedexpansion

rem 设置基础路径变量
set BASE_PATH=F:\Users\Lenovo\anaconda3\envs\insightface

rem 基于基础路径设置其他路径
set NEW_PYTHONHOME=%BASE_PATH%
set NEW_PYTHONPATH=%BASE_PATH%\Lib;%BASE_PATH%\DLLs;%BASE_PATH%\Lib\site-packages

rem 使用setx永久设置环境变量(临时方案用set)
set PYTHONHOME=%NEW_PYTHONHOME%
set PYTHONPATH=%NEW_PYTHONPATH%

rem 添加到PATH
set PATH=%BASE_PATH%;%BASE_PATH%\Scripts;%BASE_PATH%\Library\bin;%PATH%

rem 添加一个小的Python脚本来验证环境
echo import sys > check_env.py
echo print("Python executable:", sys.executable) >> check_env.py
echo print("Python prefix:", sys.prefix) >> check_env.py
echo print("Python path:", sys.path) >> check_env.py

python check_env.py
del check_env.py

rem 运行主程序
echo Running app.exe...
"%~dp0app.exe" %*

endlocal

然后就可以愉快地玩耍了。

4.静态资源使用的问题

主要目前我的解决办法是动态设定工作路径,而不使用--include-data-dir=templates=templates这种方式。
具体方式是在程序启动时,判断一下exe在哪个目录,然后将就此目录作为静态资源的起始目录。
具体代码如下

# 获取当前程序的所在目录
# Nuitka 编译后,__file__ 指向 .pyd 文件,使用 sys.argv[0] 获取可执行文件路径
if hasattr(sys, 'argv') and sys.argv[0]:
    exe_path = os.path.abspath(sys.argv[0])
    if exe_path.endswith('.exe') or exe_path.endswith('.cmd'):
        base_dir = os.path.dirname(exe_path)
    else:
        base_dir = os.path.dirname(os.path.abspath(__file__))
else:
    base_dir = os.path.dirname(os.path.abspath(__file__))

# 指定 templates 和 static 文件夹的绝对路径
app = Flask(__name__, 
            template_folder=os.path.join(base_dir, 'templates'),
            static_folder=os.path.join(base_dir, 'static'))

print("Program path:", sys.argv[0])
print("Base directory:", base_dir)
print("Templates folder:", app.template_folder)
print("Static folder:", app.static_folder)

更多推荐