前述:
我们用pytorch或者tensorflow写的算法程序在实际使用过程中都需要加密打包,我看了网上很多的教程,基本上没有一个适合作为工业级发布python项目的模板,我根据工作中的实际应用,来详细的说一下如何打包pytorch写的一个分类程序为exe可执行文件。
使用到的python模块有Cython和pyinstaller
1、首先对代码加密
使用Cython模块对代码进行加密
在项目根路径创建 build_pyd.py (文件名随意,以下命令默认此文件名)
from distutils.core import setup
from Cython.Build import cythonize
setup(
name='build_ext', # 名字随意
ext_modules=cythonize(["request/HttpService.py"]) #cythonize(["需要加密的源码文件", "需要加密的源码文件"])
)
注:入口文件不能加密
将需要加密的源码引入的依赖包复制到主入口文件
例如:HttpService.py 依赖了以下几个包
import configparser
import json
import time
import ahttp
而项目的主入口文件是 Application.py ,此时需要复制这几个依赖代码到 Application.py
原因: Pyinstaller 打包是默认加载 .pyd文件,所以没有import操作
在终端窗口执行(在build_pyd的根目录下执行)
python build_pyd.py build_ext --inplace
执行完成后会在文件对应的目录下创建 XXX.c 和 XXX.cp36-win_amd64.pyd 两个文件。其中 .c 是临时文件 .pyd 则是python的动态链接,接下来打包会引入此文件。
pyd的文件名称可以更改(去掉.cp36-win_amd64)
2、自定义配置打包
2.1 生成spec文件
为了进行自定义配置打包,需要先输出配置文件.spec文件,执行命令:
pyi-makespec -D -w main.py #(此处使用 了-D生成了一个文件夹,目的是对于大型项目,只生成一个exe文件的话,启动会特别的慢,生成一个文件夹,启动速度很快)
注:
-w 参数是为了保证不会产生黑色控制台窗口(刚开始测试,建议不要加-w,方便查看错误)
-D 参数生成一个文件目录包含可执行文件和相关动态链接库和资源文件等(默认选项,也可以不加)
2.2 添加静态资源
修改.spec文件,将项目中或者算法中依赖的数据、训练好的算法打包到生成的目录中。(假设是test.spec)
import sys
sys.setrecursionlimit(sys.getrecursionlimit() * 5) # 防止在打包时候,递归过深,打包终止
block_cipher = None
a = Analysis(['test.py'],
pathex=['/home/lixin/test'],
binaries=[],
datas=[('G:\\software\\PyCharm 2018.3.3\\jewellry\\static\\categoryType.names','.'),
('G:\\software\\PyCharm 2018.3.3\\jewellry\\static\\colourType.names','.'),
('G:\\software\\PyCharm 2018.3.3\\jewellry\\static\\technoType.names','.'),
('G:\\software\\PyCharm 2018.3.3\\jewellry\\static\\checkpoint\\100_best_resnext.pth','.')], ## <---- 修改此处添加外部文件.含义:将该路径下的文件拷贝到生成文件夹的根目录下。
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
name='test',
debug=False,
strip=False,
upx=True,
runtime_tmpdir=None,
console=True )
注:个人建议将所有的静态文件放到与引用该文件的py文件相同的根目录下。
2.3 打包
pyinstaller test.spec
此时生成两个文件夹,build以及dist,dist中的test文件夹即为发布的文件夹,可以看到,
在datas中配置的文件被copy到了里面与可执行文件是同目录,完成!
2.4 可能出现的问题
1)打包完成后,此时 dist下只有一个可执行文件,如果运行这个可执行文件,出现错误,找不到 ‘test.txt’ !!
原因是:运行可执行文件时,会先将可执行文件进行压缩,压缩的位置在 /tmp 下,再执行,所以被打包进去的数据文件在被解压的路径下,而程序是在运行的路径下搜索,即可执行文件的目录下,所以找不到数据文件。
添加如下代码:
# 根据文件所在的位置,得到该文件的绝对路径
import os
import sys
def resource_path(relative_path):
""" Get absolute path to resource, works for dev and for PyInstaller """
base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
return os.path.join(base_path, relative_path)
获取到pyinstaller临时文件夹的位置,再此位置进行文件搜索,即test.py文件修改如下:
import os
import sys
def resource_path(relative_path):
""" Get absolute path to resource, works for dev and for PyInstaller """
base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
return os.path.join(base_path, relative_path)
file = 'text.txt'
print(resource_path(file)) ## <----- 打印一下看看是否在临时文件夹下搜索
with open(resource_path(file), 'r') as f:
while True:
line = f.readline() # 逐行读取
if not line:
break
print(line)
重复以上步骤,在dist中运行可执行文件,得到如下结果:
(/tmp/_MEIY5Vljn/ 为我的pyinstaller临时文件夹)
/tmp/_MEIY5Vljn/test.txt
即将你在代码中所有使用到路径的地方,替换为这个方法即可。
2)加密文件的时候报错:
/Main.py:367: FutureWarning: Cython directive 'language_level' not set, using 2 for now (Py2). This will change in a later release! File: XXXXXX.pyx
tree = Parsing.p_module(s, pxd, full_module_name)
解决方案: 在你需要加密的py文件的最顶上加一句话:
# cython:language_level=3
再次编译即可。