Flask项目能打包为单个exe文件运行?知道原理后简直不要太简单!

python打包exe工具

Python打包工具主要有三种:py2exe、PyInstaller和cx_Freeze。虽然是三种工具,但是实现打包的方式都是大同小异。无非将Python运行所需的基础dll文件和源码依赖的模块筛选后聚合在一起,从而达到脱离环境单独运行的目的。但其中比较新奇的是它们居然可以将最终代码打包成单个文件去运行,简直不要太神奇。
清风常用的打包工具为pyinstaller,安装下载简单,网上的文档也很齐全。

打包的exe如何执行

但凡大家看到python打包exe工具的优势,都会提到一句打包成单个文件,可以保护源码不外泄。对于新手来说,这个理由完美,单个文件怎么操作感觉也不会获得源码,但我只能说,天真啊!
pyinstaller的官方文档:
pyinstaller.readthedocs.io:How the One-File Program Works
中有相关内容的详细说明,为了方便,我简单翻译下:

引导加载程序也是单文件捆绑包的核心。启动后,它将在此操作系统的相应临时文件夹位置中创建一个临时文件夹。该文件夹名为_MEIxxxxxx其中xxxxxx是一个随机数。一个可执行文件包含脚本使用的所有Python模块的嵌入式归档,以及任何非Python支持文件(例如,.so文件)的压缩副本。引导加载程序解压缩支持文件,并将副本写入临时文件夹。这可能需要一些时间。这就是为什么单文件应用程序比单文件夹应用程序启动慢的原因。

所以单个的exe文件在执行时,会先在系统临时目录下创建一个_MEI开头的文件夹,然后解压源码、依赖文件后,运行该临时文件夹下的内容。
其中windows的临时文件夹通常为:
C:\Windows\Temp_MEIxxxx 或
C:\Users\用户名\AppData\Local\Temp_MEIxxxx

但经过多次测试,几乎全在后者的目录下。其实只要在cmd下输入echo %temp% 或者 %tmp%就能确定了...

windwos临时目录

Linux的临时文件夹目录自然是在:
**/tmp/_MEIxxxxx **

当然,如果每次exe执行时,都会创建临时文件夹,但执行完成后又不销毁,岂不是早就导致我们的电脑磁盘空间溢出了。所以官方是有说明的:

创建临时文件夹后,引导程序将在临时文件夹的上下文中继续与单文件夹捆绑软件一样进行。当捆绑的代码终止时,引导加载程序将删除临时文件夹。
如果程序崩溃或被杀死,则不会删除该文件夹(在Unix上为kill -9,在Windows上为Task Manager杀死,在Mac OS上为“ Force Quit”)。因此,如果您的应用程序频繁崩溃,则用户将丢失磁盘空间到多个临时文件夹。_MEIxxxxxx_MEIxxxxxx

打包Flask项目

pyinstalelr的基础使用就不在这里过多介绍了,之前的文章有过详细的说明:
Python打包工具--Pyinstaller详细介绍:https://mp.weixin.qq.com/s/smsO0n8M18J7ofoOsWEjoQ
我们只需要知道pyinstaller -F(onefile)参数即可将代码最终打包为单个的exe文件即可。
但是Flask的static、templates该怎么打包呢?让我们以之前开发过的一个FlaskHttpserver为例说明。
首先看下代码结构:

Flask代码目录

settings中放了Flask的一些config配置,manage.py通过蓝图注册HttpServer中views下的account与home模块。那么,现在我们需要将代码中的static、templates、settings(测试发现这个配置文件也没办法自动打包,需要手动追加)成单个文件呢?pyinstaller提供了一个[--add-data ]的参数,整体打包命令如下:

 pyinstaller -F -i BreezePython.ico --add-data="HttpServer\static;HttpServer\static" --add-data="HttpServer\templates;Httpserver\templates" --add-data="settings.py;." manage.py

原理就是保持代码中的路径一致,如果是当前路径使用.进行替换。
有些人觉得这个一个一个的添加太麻烦了,那么还有另一种思路。来看看我们打包后的目录:


spec文件的妙用

打包完成后,会生成一个main应用.spec的文件,通过我们刚才一顿--add-data的操作后,spec有什么区别么?


通过datas添加数据

所以我们可以换另一种方式加载依赖文件:
  1. 首次打包时直接-F 完成打包
  2. 编辑*.spec文件,通过在列表中添加对应元祖信息的方式,追加以来稳健
  3. pyinstaller -F *.spec进行二次打包即可追加文件至exe中。

来让我们看看打包后的exe是否可以执行吧:


打包效果展示

OK,一个exe文件拉起整个Flask项目,带着exe我们就可以脱离环境单独运行我们的HTTPServer了。是不是很炫酷?

​临时文件监控复制

初次测试,可能存在打包路径错误的问题,每次去找临时路径查看太麻烦了,既然写代码,不如顺手写个动态监控_MEI路径并完成循环复制的功能,具体实现如下:

  1. 判断电脑的操作系统
  2. while循环监控临时目录
  3. 启动exe工具
  4. 获取exe创建的_MEI开头文件夹
  5. 将该临时文件夹拷贝到执行目录

最终代码实现如下:

# -*- coding: utf-8 -*-
# @Author   : 王翔
# @微信号   : King_Uranus
# @公众号    : 清风Python
# @GitHub   : https://github.com/BreezePython
# @Date     : 2020/11/17 23:50:09
# @Software : PyCharm
# @version  :Python 3.7.3
# @File     : get_source_code.py

import platform
import os
import time
import shutil


def get_tmp_path():
    if platform.platform().lower().startswith('windows'):
        return os.getenv('temp')
    else:
        return '/tmp'


class GetSourceCode:
    def __init__(self):
        self.base_path = os.path.dirname(__file__)
        self.tmp_path = get_tmp_path()
        self.basic_dirs = self.get_dirs()
        self.code_dir = None

    def get_dirs(self):
        for root, dirs, files in os.walk(self.tmp_path):
            return set(dirs)

    def get_source_dir(self):
        while True:
            _dir = list(self.get_dirs() - self.basic_dirs)
            if _dir and _dir[0].startswith('_MEI'):
                self.code_dir = _dir[0]
                print("find source code dir %s" % self.code_dir)
                break
            else:
                time.sleep(0.2)
        self.copy_code_dir()

    def copy_code_dir(self):
        abs_tmp_path = os.path.join(self.tmp_path, self.code_dir)
        while os.path.exists(abs_tmp_path):
            source_path = os.path.join(self.base_path, self.code_dir)
            if not os.path.exists(source_path):
                os.mkdir(source_path)
            for root, dirs, files in os.walk(abs_tmp_path):
                for file in files:
                    remote_path = root.replace(abs_tmp_path, source_path).replace('\\', '/')
                    if not os.path.exists(remote_path):
                        print(remote_path)
                        os.makedirs(remote_path)
                    if not os.path.exists(remote_path + '/' + file):
                        shutil.copy(os.path.join(root, file), remote_path)
        print("Get source code end.")


if __name__ == '__main__':
    print("start Source Code Analyse project.")
    print("Monitoring source files...")
    g = GetSourceCode()
    g.get_source_dir()

看看效果如何:


动态拷贝源码数据

ok,快把你积攒已久的代码筛选下,看看那些适合打包成exe,拿去给朋友们炫耀吧!
关于FlaskHttpserver,如果需要的朋友可以后台回复"服务"进行下载。
看到这里还不关注、点赞、转发下?听说这样三连操作,写代码没BUG哦!

The End

期待你关注我的公众号清风Python,如果你觉得不错,希望能动动手指转发给你身边的朋友们。
我的github地址:https://github.com/BreezePython

你可能感兴趣的:(Flask项目能打包为单个exe文件运行?知道原理后简直不要太简单!)