跳至主要內容

PyInstaller快速上手教程

熊大大约 4 分钟

PyInstaller

安装

pip install git+https://github.com/pyinstaller/pyinstaller

参数

参数作用
-F 文件.py生成单一的exe文件,产生一个文件用于部署常用
-w 文件.py使用Windows子系统执行.当程序启动的时候不会打开命令行,去掉控制台窗口,对于执行文件没有多大的用处,一般用于GUI界面使用,(只对Windows有效)
-i 图标路径可执行文件的图标(只对Windows系统有效)
-D生成一个文件夹包括依赖文件,产生一个目录用于部署 (默认)常用
-p 搜索路径添加Python使用的第三方库,添加必要的搜索路径,自定义需要加载的类路径,一般情况用不到
-K在部署时包含 TCL/TK
-c使用控制台子系统执行,控制台无窗口(默认)(只对Windows有效)
--add-data "img;img"打包时包含非py文件的资源

图片打包的方法

  1. 可以把图像文件转换成py文件中的变量。py有这个工具,也可能是wxpy有。

  2. 可以把图像文件放到程序下的一个文件夹里,随程序分发。

  3. 放到exe里肯定也可以,你得写好setup文伴,确保把图像文件包含在内。

# 首先先打第一包
pyinstaller main.py
# 在当前目录下打开main.spec文件,将图片资源文件夹加载进来
a.datas += [('img','E:\\svnProject\\shenjidaili_project\\winProject\\img','DATA')]
# 再次使用spec文件打包
pyinstaller main.spec


如何将资源文件一起打包至exe中

神鸡代理软件打包示例

  1. 创建零食目录路径查找函数
  2. 将所有图片路径加上查找函数
#>>> vim utils.py

import sys
import os
import re


# 在 windows 下将程序打包成 exe 的库,会将一个变量 frozen 注入到 sys 中。可以用来确定当前运行的程序是脚本文件还是exe打包程序
# 生成资源文件目录访问路径
def resource_path(relative_path):
    if getattr(sys, 'frozen', False): # 是否Bundle Resource
        # base_path = os.path.dirname(os.path.abspath(sys.executable))
        # base_path = os.path.dirname(sys.executable)
        # os.chdir(base_path)

        base_path = sys._MEIPASS
    else:
        base_path = os.path.abspath(".")
    return os.path.join(base_path, relative_path)


# 仅仅做为脚本函数,修改文件内容的图片路径
def switch_path(file_name):
    regex_str = r'u*[\"\']img/.*?[\"\']'
    pattern = re.compile(regex_str)

    # 读取文件内容
    f = open(file_name, 'r', encoding="utf-8")
    alllines = f.readlines()
    f.close()

    # 匹配字符串
    str_list = []
    for eachline in alllines:
        one_str_list = pattern.findall(eachline)
        str_list += one_str_list

    # 替换字符串并写入文件
    f = open(file_name, 'w+', encoding="utf-8")
    for eachline in alllines:
        newline = None
        for resource_str in str_list:
            if eachline.find(resource_str) != -1:
                newline = eachline.replace(resource_str, "resource_path(" + resource_str + ")")
                # a = re.sub(resource_str, "resource_path("+ resource_str + ")", eachline)
                break
        if newline:
            f.writelines(newline)
        else:
            f.writelines(eachline)
    f.close()

    # 插入一行
    with open(file_name, 'r+', encoding="utf-8") as f:
        content = f.read()
        f.seek(0, 0)
        f.write('from utils import resource_path\n')


if __name__ == "__main__":
    switch_path("build_frame.py")
python utils.py
  1. 打包时加上img图片资源目录

    pyinstaller -i "favicon.ico" -F -w --add-data "img;img" main.py
    
    # 调试模式
    pyinstaller -i "favicon.ico" -F --add-data "img;img" main.py
    

https://www.cnblogs.com/darcymei/p/9397173.html

多进程打包

官方打包解释:https://github.com/pyinstaller/pyinstaller/wiki/Recipe-Multiprocessingopen in new window

  1. 新建自定义一个多进程模块(照写)

    #>>> vim mul_process_package.py
    
    import os
    import sys
    import multiprocessing
     
    # Module multiprocessing is organized differently in Python 3.4+
    try:
        # Python 3.4+
        if sys.platform.startswith('win'):
            import multiprocessing.popen_spawn_win32 as forking
        else:
            import multiprocessing.popen_fork as forking
    except ImportError:
        import multiprocessing.forking as forking
     
    if sys.platform.startswith('win'):
        # First define a modified version of Popen.
        class _Popen(forking.Popen):
            def __init__(self, *args, **kw):
                if hasattr(sys, 'frozen'):
                    # We have to set original _MEIPASS2 value from sys._MEIPASS
                    # to get --onefile mode working.
                    os.putenv('_MEIPASS2', sys._MEIPASS)
                try:
                    super(_Popen, self).__init__(*args, **kw)
                finally:
                    if hasattr(sys, 'frozen'):
                        # On some platforms (e.g. AIX) 'os.unsetenv()' is not
                        # available. In those cases we cannot delete the variable
                        # but only set it to the empty string. The bootloader
                        # can handle this case.
                        if hasattr(os, 'unsetenv'):
                            os.unsetenv('_MEIPASS2')
                        else:
                            os.putenv('_MEIPASS2', '')
     
        # Second override 'Popen' class with our modified version.
        forking.Popen = _Popen
    
  2. 修改程序进入文件

    #>>> vim main.py
    
    import mul_process_package  # 导入刚建的模块
    import multiprocessing
    
    
    if __name__ == '__main__':  # 这个重要,必须要加
        multiprocessing.freeze_support()
    

如果要在windows下使用multiprocessing,必须在代码入口添加multiprocessing.freeze_support( ),否则用pyinstaller打包成exe,执行中会出现主进程无限循环的问题。

subprocess模块失效问题

​ 使用pyinstaller将python程序打包,不使用-w参数时(如“pyinstaller -F main.py -i cat.ico”)程序运行正常,但使用-w参数去掉console控制台后,程序便卡在了一个地方无法继续运行,经过调试发现,出问题的地方在subprocess.Popen语句。

错误代码示范

popen = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

正确代码示范

# 添加stdin=subprocess.PIPE参数
popen = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

# 当去掉终端窗口时,程序没有等待命令执行结果就退出了,所以添加等待就可以了
popen.wait()

# 
popen.stdout.close()
popen.stderr.close()

stdin=subprocess.PIPE参数是一定要有。

shell=Ture:不会弹出console;shell=False:会弹出一个console

其他解决办法(未尝试过)

# 在创建进程时,加上startupinfo参数
si = subprocess.STARTUPINFO()
si.dwFlags|=subprocess.STARTF_USESHOWWINDOW
mProcess=subprocess.Popen(cmd,stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE,startupinfo=si)