封装打包Python脚本

一、前言

封装打包Python的好处,节省了安装各类各样包依赖的问题,同时能够增强咱们代码隐私的安全性,这里个人演示环境是Python3.6 ,CentOS7的系统,同时打包工具采用pyinstaller。python

二、环境准备

2.1 Python共享so模块

默认Python模块是私有的,咱们想打包就须要将咱们的so模块变为共享的,那么咱们须要执行两个操做便可。linux

  1. 从新编译Python,加入编译参数 --enable-shared
  2. 将so共享库加入到系统之中
[root@c7-work-1 ~]# cat /etc/ld.so.conf.d/python3.6.conf   
/usr/local/python3.6.5-shared/lib
[root@c7-work-1 ~]# ldconfig

完成上面操做便可了。安全

2.2 安装pyinstaller

pip install pyinstaller

2.3 准备工程

这里咱们只须要准备工程和相关依赖包,而且安装成功便可,程序能正常跑则没问题。bash

这里根据本身经验来便可了,没啥技巧。编辑器

三、代码调整

默认状况下,咱们打包后有些代码原有的获取方式会有些转变。工具

3.1 运行时的环境

当应用程序运行时,可能须要访问如下三个常规位置中的任何位置的数据文件:测试

  • 捆绑在一块儿的数据文件;
  • 用户的配置文件;
  • 用户工做目录中的文件

在Python中咱们须要注意提防以下:ui

P1: 使用__file__sys._MEIPASSspa

当程序未冻结时,标准Python变量__file__是如今正在执行的脚本的完整路径。当捆绑的应用程序启动时,引导加载程序会设置sys.frozen属性并将绝对路径存储到bundle文件夹中sys._MEIPASS。对于单文件夹捆绑包,这是该文件夹的路径,不管用户在哪里放置它。对于单文件包,这是 引导加载程序建立的临时文件夹的路径(请参阅单文件程序的工做原理)。_MEIxxxxxx命令行

P2: 使用sys.executablesys.argv[0]

当正常的Python脚本运行时,sys.executable是执行程序的路径,即Python解释器。在一个冻结的应用sys.executable程序中,也是执行程序的路径,但这不是Python; 它是单文件应用程序中的引导加载程序或单文件夹应用程序中的可执行文件。这为您提供了一种可靠的方法来查找用户实际启动的冻结可执行文件。

sys.argv[0]是用户命令中使用的名称或相对路径。它多是相对路径或绝对路径,具体取决于平台以及应用程序的启动方式。

下面是一个演示代码,有兴趣能够执行下看看效果:

#!/usr/bin/python3
import sys, os
frozen = 'not'
if getattr(sys, 'frozen', False):
        # we are running in a bundle
        frozen = 'ever so'
        bundle_dir = sys._MEIPASS
else:
        # we are running in a normal Python environment
        bundle_dir = os.path.dirname(os.path.abspath(__file__))
print( 'we are',frozen,'frozen')
print( 'bundle dir is', bundle_dir )
print( 'sys.argv[0] is', sys.argv[0] )
print( 'sys.executable is', sys.executable )
print( 'os.getcwd is', os.getcwd() )

3.2 调整项目代码

根据上面的规则,着重调整一下代码。

原代码:

BASE_PATH = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))

New代码:

if getattr(sys, 'frozen', False):
    BASE_PATH = sys._MEIPASS
else:
    BASE_PATH = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))

只须要启动入口脚本调整便可,其余脚本无需调整

四、打包封装

打包:

pyinstaller thunder_predict.spec

如何生成 *.spec文件呢,能够这里直接pyinstaller pyscript便可,而后在修改调整;下面提供个人实例文件:

# -*- mode: python -*-

block_cipher = None


a = Analysis(['predict/thunder_predict.py'],
             pathex=['.'],
             binaries=[],
             datas=[('model', 'model'), ('dataset', 'dataset')],
             hiddenimports=['cython','sklearn','sklearn.ensemble','sklearn.neighbors.typedefs',
                            'sklearn.neighbors.quad_tree','sklearn.tree._utils','scipy._lib.messagestream'],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          [],
          exclude_binaries=True,
          name='thunder_predict',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          console=True )
coll = COLLECT(exe,
               a.binaries,
               a.zipfiles,
               a.datas,
               strip=False,
               upx=True,
               name='thunder_predict')

五、打包So文件库

咱们为何要打包成为so文件呢?

Python有py、pyc、pyw、pyo、pyd等文件格式。

其中,pyc是二进制文件。但很容易被反编译。

pyw也不行,只是隐藏命令行界面而已,能够做为入口脚本。

pyo和pyc差很少,也容易被反编译。

最后剩下pyd格式。pyd格式是D语言(C/C++综合进化版本)生成的二进制文件,实际也会是dll文件。该文件目前位置没找到能够被反编译的消息,只能被反汇编。Sublime text编辑器也是使用该格式。

5.1 打包So模块

打包生成so文件:

python3 ../utils/build_so.py build_ext --inplace

清理无用的文件:

rm -fr build main.c main.py

测试(成功测试成功):

[root@c7-work-1 thunder]# python3 predict/thunder_predict.py 01:dXBsb2FkRGF0YTE1MzI2Nzk4MzQzNDI= 199
success

Cython脚本内容:

from distutils.core import setup
from Cython.Build import cythonize

"""
python build_so.py build_ext --inplace
"""

setup(
  name = 'main',
  ext_modules = cythonize("main.py"),
)

5.2 从新打包整个

命令以下:

[root@c7-work-1 thunder]# pyinstaller thunder_predict.spec 
36 INFO: PyInstaller: 3.4
37 INFO: Python: 3.6.5
................ 省略一下信息

咱们检查一下是否已经将咱们的So库文件打包。

[root@c7-work-1 thunder]# ls dist/thunder_predict/main.cpython-36m-x86_64-linux-gnu.so 
dist/thunder_predict/main.cpython-36m-x86_64-linux-gnu.so

完美。

5.3 完美之余有些略显通俗的坑

Q: pyinstaller利用获取脚本中import来来打包对应的库,若是你打包成so文件了还能够?

A: 这里我经过Python PKG的方式,将so会差异的库放入到__init__.py中,就能够解决这个问题。