使用pyinstaller打包你的桌面应用 – PyQt5教程后记

  • xhyl
  • March 1, 2020

当你写好一个python应用以后(有可能是命令行,有可能是GUI),你或许希望分享给他人使用,而别人可能并没有python环境,那么我们需要寻找一种方法生成可执行文件(比如Windows上的exe或macOs上的app)

当你有打包你的python应用的需求以后,你可能会像搜索引擎寻求帮助,你或许曾经搜索过“python 转 exe”,“python打包”等等这样的字眼,你或许曾看到过各种各样的相关解决方案,本文介绍的是其中的一种,但是可以负责任的说,这是目前最好的方案,并且是跨平台最好的方案,也就是说使用这个叫pyinstaller的工具,你可以把同样的代码打包在Windows,Linux以及macOs上运行。

这个工具的名称就是pyinstaller,官方主页:
http://www.pyinstaller.org/

这个工具将打包工作封装成一条简单的指令就能完成的动作。

准备

工具准备

第一件事情当然是安装这个库了,和往常一样,使用pip

1
pip install pyinstaller

源代码准备

因为我们是针对一个GUI应用来打包,那么我们需要简单的改造一下源代码,如果你是在打包命令行,则不需要进行这个操作。

首先,我们要创建一个入口文件,名称为 wifidrop.pyw,名字无所谓,它将成为你最终的软件名称,这里需要注意的是,我们的文件后缀为pyw,比py文件后缀多了一个w,而w的意思是window,也就是说告诉python环境,我们这个软件是一个窗口应用,如果不这么做,在运行软件的时候,除了有我们的GUI界面以外,python还会留 一个命令行窗口在后边,这显然不是我们想要的,而使用了pyw后缀以后,python则不会显示命令行窗口。

在 wifidrop.pyw 不需要做什么太多东西,仅仅是简单的调用main.py中的main函数就可以了。

1
2
3
4
5
6
import sys
from main import main

if __name__ == '__main__':
    excode = main()
    sys.exit(excode)

打包

做好代码相关的准备以后,就可以使用pyinstaller来打包软件了,过程也非常简单,一行命令就能解决,在命令行中运行

1
pyinstaller --clean -F -w wifidrop.pyw -i Treetog-I-Documents.ico

参数说明

  • –clean :告诉pyinstaller删除缓存和临时文件
  • -F :告诉pyinstaller将打包的结果放在一个exe文件中,也就是说最终结果将只有一个exe文件,如果不使用这个参数,那么结果会是一个exe加很多依赖文件,不利于我们分发软件。
  • -w :告诉pyinstaller我们要生成的是一个窗口应用
  • -i :为我们的应用指定一个图标,否则默认的话会使用python图标

一般来说,这些参数就足够日常使用了,如果需要更深入的功能,比如加密等等,就需要阅读手册了。

这条命令成功运行完以后,你将会在项目目录中看到build和dist两个文件夹,最终的可执行文件就放置在dist文件家中。

针对PyQt应用的改进

目前的小问题

当我们尝试运行在dist中生成的wifidrop.exe文件的时候,你将发现,程序无法运行,不知道你还有没有印象,至今为止,我们都一直在main.py中使用loadUi函数加载Qt Creator创建的ui的方式来创建GUI界面,那么仔细观察一下dist文件夹中,里边并没有ui文件,对吧?

因为pyinstaller仅仅是一个py应用的打包工具,它并不知道其他的文件的存在,所以,你可以尝试把我们的两个ui文件拷贝到dist文件夹中之后再运行这个exe。

你会发现现在应用可以正常使用了。但是这样会不会有什么风险?

对了,ui文件说白了是一个xml格式的文本文件,如果使用我们软件的人有意或者无意的修改了这两个文件中的内容,我们的软件极有可能就没办法运行了,因此,在分发我们的软件的时候,要避免使用ui文件。

ui文件的好处是在开发的时候,可以迅速的通过Qt Creator修改UI。

解决办法

当然PyQt也提供了很好的解决办法,它提供了一个小脚本,可以帮我们把ui文件转成py文件,并将UI转换成类。我们通过运行下边这两条命令,将mainwindow和dialog转成python类。

1
2
python -m PyQt5.uic.pyuic -x dialog.ui -o dialog.py
python -m PyQt5.uic.pyuic -x mainwindow.ui -o mainwindow.py

每一个ui文件会对应一个py文件,而py文件中会有相应的类。

有了py模块以后,我们还需要在实例化ui的地方(也就是main.py)中将加载ui文件的方式改为实例化python类的方式来加载ui,分别要修改MainWindow和SendDialog的初始化函数,选择2 option的方式来加载ui

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class MainWindow(QMainWindow):
    """Main window"""
    def __init__(self):
        super(MainWindow, self).__init__()

        # UI setup - 1 option
        # dynamic load ui for development purpose
        # self.ui = loadUi('./mainwindow.ui', self)

        # Use py to setup UI - 2 option
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        self.setStatusBar(None)  # https://doc.qt.io/qt-5/qmainwindow.html#setStatusBa
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class SendDialog(QDialog):
    def __init__(self, url_list, socket_server_thread, device_discover_thread, socket_broadcast):
        super(SendDialog, self).__init__()

        # UI setup - 1 option
        # dynamic load ui for development purpose
        # self.ui = loadUi('./dialog.ui', self)

        # Use py to setup UI - 2 option
        self.ui = Ui_dialog()
        self.ui.setupUi(self)

再次打包

代码修正之后,我们来使用上边相同的pyinstaller命令打包,成功运行后,点击生成的wifidrop.exe,你会发现在无需ui文件的情况下,软件也能很好的运行了。

代码仓库说明

本文中用到的打包命令和ui转py命令,可以在github 仓库 https://github.com/pythonlibrary/wifidrop 中的 tutorial-6-direct-dist 和 tutorial-6-dist tag下找到,这两个tag的区别是:前一个使用了加载ui文件的方式显示UI,后边一个修正了这个问题,使用类实例化的方式显示UI。

类似文章

PyQt5教程5 – 最后一环,完善接收功能

PyQt5教程5 – 最后一环,完善接收功能

  • xhyl
  • February 23, 2020

前边几篇文章中我们已经将发送和界面功能实现, 对于一个文件收发软件,它既可以是主机来发送文件,也可以作为目标机来接收文件,在这一篇文章中,我们将实现剩下所有的功能,即完成接收功能。 为了更直观的给大家展示本节的最终实现目标,我们先把实现结果的视频放在最前边,因为一台电脑不能给自己发送文件,因此,本节的测试需要使用两台电脑,并且这两天电脑需要连接到同一个局域网中。

阅读更多
PyQt5教程4 – 发现局域网的其他客户端

PyQt5教程4 – 发现局域网的其他客户端

  • xhyl
  • January 29, 2020

到目前为止我们已经有了比较完整的用户交互界面,在使用airdrop发送文件的时候,用户可以选择发送给哪一台设备,在这一章中,我们在PyQt5教程3 – 发送页面进度条,自定义信号槽和线程 的基础上在WiFi Drop上实现类似的功能,即当我们选中文件发送的时候,接下来软件需要能够搜索到局域网中其他的客户端。

阅读更多
PyQt5教程3 – 发送页面进度条,自定义信号槽和线程

PyQt5教程3 – 发送页面进度条,自定义信号槽和线程

  • xhyl
  • January 22, 2020

在airdrop,当用户想要发送文件出去的时候,需要选择目标机器或者用户,在随后的发送中还会显示发送进度,针对这一交互逻辑,在WiFidrop中,我们也将设计相同的交互逻辑,我们要增加一个发送页面,在这个页面上将能够显示我们可以发送的对象,以及一个进度条。 这一篇文章中,我们将继续在 PyQt5教程2:主页面和拖动 的基础上,加入发送页面,和一些相应的逻辑。

阅读更多