python多文件打包exe并实现自动更新

前言:最近项目上需要设计一个自动分析工具,要求做出来后在每个使用者(同事)电脑上都能独立运行,且方便自动更新。为此特意研究了pyinstaller来实现多文件打包成exe并能自动更新。

一、pyinstaller安装和使用说明

在pycharm里搜索pyinstaller安装,或在terminal界面输入如下安装:
pip install pyinstaller
安装完成后,可用pyinstaller将一个或多个py文件打包成exe,并可根据不同需求生成不同打包形式,同时在terminal界面输入如下指令查看各代码对应的形式。
pyinstaller -h
现例举几个最常用的指令加以解释说明,
请添加图片描述请添加图片描述

二、多文件打包成exe

关于多个文件打包成exe,各博客大佬汇总出的方法主要是2种:
1、pyinstaller [主文件] -p [其他文件1] -p [其他文件2]
2、使用spec方式
本文将依次试验这两种方法并探究各自的实用性和优缺点。为此本文举一个例子说明:设计一个程序用于计算某打工人扣除五险一金+个税后的实际到手收入。
程序结构如下:

  • 主程序
main.py
import os
from ensurance import calcu5_ensure
from income_tax import calcu_tax
​
if __name__ == '__main__':
    base, extra = input("please input tax before income:").split()
    base, extra = float(base), float(extra)
    ensure5 = calcu5_ensure(base)
    inc_tax = calcu_tax(base)
    tax_after = base + extra - ensure5 - inc_tax
    print(f"Final tax-after income is {tax_after}.")
    temps = input("\n")
  • 计算五险一金
# ensurance.py
def calcu5_ensure(tax_before):
   five_per = [0.08, 0.02, 0, 0.003, 0, 0.12]
   five_per_count = [round(i * tax_before, 2) for i in five_per]
   return sum(five_per_count)
  • 计算个税(随便取的百分比)
# income_tax.py
def calcu_tax(base):
   return base * 0.1

接下来就分别使用这两种方式进行打包。

2.1 pyinstaller [主文件] -p [其他文件1]

输入如下
pyinstaller main.py -p ensurance.py -p income_tax.py
打包成功!
并在.\dist\main路径下可找到main.exe文件,
在这里插入图片描述
双击exe文件可正常运行,
在这里插入图片描述
但是,针对于文件较多的时候使用-p的方法就比较麻烦,这种时候可采用方案2。

2.2 pyinstaller xx.spec

① 只打包主程序mian.py以生成.spec文件并只保留这个文件,删除目录下生成的dist和build文件
pyinstaller -F main.py
② 更改.spec文件内容,补充想要一并打包的py文件或其他resource文件等。spec文件内容如下(步骤①若选了-F则不会有coll)
在这里插入图片描述
式中参数的含义如下,
在这里插入图片描述
其中a和exe里的改动较多,pyz和coll基本没有要改的。且针对于a和exe中主要参数的含义如下,
在这里插入图片描述
回到刚才的例子,.spec文件需要修改第一行元素,
添加图片注释,不超过 140 字(可选)
而后在terminal中输入,
pyinstaller main.spec
此时在.\dist路径下就能找到打包好了的main.exe,经验证该方法生成的exe也可正常运行。
在这里插入图片描述

三、exe自动更新

根据项目需求结合我司已有的远端服务器,假定主程序是main.exe,在每次更新的版本后我都会生成一个标明当前版本的xx.txt文件,升级工具是AutoClient.exe,他们的存放形式如下,
在这里插入图片描述
我的设计思路如下,
在这里插入图片描述

Fig 3.1

解读一下:
核心逻辑是运行AutoClient.exe这个升级工具,判断本地路径下的程序版本(读取xx.txt可识别)和远端路径下的版本是否一致,若不一致则需删除旧版本、下载新版本;若一致则无需更新。最后再调用最新版本的main.exe启动主程序。
所以第一步要先访问到服务器。

3.1 python连接到服务器

博客大佬有用NFS,FTP等服务器都可以实现,本文连接的是SFTP服务器。
安装paramiko库即可调用sftp,为方便调用我特意将sftp常用的几个功能封装在了MySFTP()类里,

class MySFTP():
    def __init__(self):
        self.sftp = None
        self.ssh = paramiko.SSHClient()
        self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
​
        # 重新设置下编码方式
        self.ssh.encoding = 'gbk'
        self.log_file = open("log.txt", "a")
        self.file_list = []
​
    def login(self, host_name, port, user_name, password):
        """
​
        :param host_name:
        :param port:
        :param user_name:
        :param password:
        :return:
        """
        try:
            self.debug_print('开始尝试连接到 %s' % host_name)
            self.ssh.connect(host_name, port, user_name, password)
            self.debug_print('成功连接到 %s' % host_name)
​
            self.debug_print('开始尝试登录到 %s' % host_name)
            self.sftp = self.ssh.open_sftp()
            self.debug_print('成功登录到 %s' % host_name)
        except Exception as err:
            self.deal_error("SFTP 连接或登录失败 ,错误描述为:%s" % err)
​
    def upload(self, local_path, remote_path):
        self.sftp.put(local_path, remote_path)
        self.debug_print('文件上传成功')
​
    def download(self, remote_path, local_path):
        self.sftp.get(remote_path, local_path)
        self.debug_print('文件下载成功')
​
    def delete(self):
        pass
​
    def listdir(self, remote_path):
        return self.sftp.listdir(remote_path)
​
    def debug_print(self, s):
        """ 打印日志
        """
        self.write_log(s)
​
    def write_log(self, log_str):
        """ 记录日志
            参数:
                log_str:日志
        """
        time_now = time.localtime()
        date_now = time.strftime('%Y-%m-%d %H:%M:%S', time_now)
        format_log_str = "%s ---> %s \n " % (date_now, log_str)
        print(format_log_str)
        self.log_file.write(format_log_str)
​
    def deal_error(self, e):
        """ 处理错误异常
            参数:
                e:异常
        """
        log_str = '发生错误: %s' % e
        self.write_log(log_str)
        sys.exit()

3.2 记录版本

将版本信息写入txt文件名上,将版本改动内容记录在txt文件里。

def write_version_txt():
    """
    保留每一版的修改信息
    :return:
    """
    text_dic = {}
    # v1版改动内容————————————————————————————————————————————————————————————————
    cur_version = 'version_v1'
    text_dic[cur_version] = "Establish the color analysis tool."
​
    # v2版改动内容————————————————————————————————————————————————————————————————
    # cur_version = 'version_v2'
    # text_dic[cur_version] = "Change the color std."
​
    # 汇总各个版本的改动,生成txt
    cur_ver_txt = f"{cur_version}.txt"
    # if os.path.exists(cur_ver_txt):
    #     os.remove(cur_ver_txt)
    with open(cur_ver_txt, "w") as f:
        json_str = json.dumps(text_dic, indent=0, ensure_ascii=False)
        f.write(json_str)
        f.write('\n')
    print(f"{cur_ver_txt} is generated!")

3.3 AutoClient.py实现自动更新逻辑

class UpdateFile():
    def __init__(self, local_folder, remote_folder):
        self.local_folder = local_folder
        self.remote_folder = remote_folder
        self.func = MySFTP()
        host_name, port, user_name, password = 'xx, 22, 'yy', 'zz'  # 此处需填写SFTP域名、端口、账号、密码
        self.func.login(host_name, port, user_name, password)
​
    def down_pkg(self):
        local_pkg_folder = self.local_folder + '/package/'
        remote_pkg_folder = self.remote_folder + '/package/'
        os.mkdir(local_pkg_folder)
        files = self.func.listdir(remote_pkg_folder)
        for file in files:
            self.func.download(remote_pkg_folder + file, local_pkg_folder + file)
​
    def get_cur_version(self):
        self.package_folder = self.local_folder + '/package'
        # 【1】get current version
        files = os.listdir(self.package_folder)
        local_ver_file = [file for file in files if 'version' in file][0]
        pattern = re.compile("version_(.*?).txt")
        cur_version = pattern.findall(local_ver_file)[0]
        print(f"Current version is {cur_version}")
        return cur_version
​
    def init_run(self):
        self.func.download(remote_folder + '/AutoClient.exe', local_folder + '/AutoClient.exe')
        self.down_pkg()
        self.func.debug_print(f"下载成功!")
​
    def update_run(self):
        files = self.func.listdir(remote_folder + '/package')
        remote_ver_file = [file for file in files if 'version' in file][0]
        pattern = re.compile("version_(.*?).txt")
        remote_version = pattern.findall(remote_ver_file)[0]
​
        cur_version = self.get_cur_version()
        if cur_version != remote_version:
            shutil.rmtree(self.package_folder)
            self.down_pkg()
        self.func.debug_print(f"{remote_version}更新成功!")
​
​
if __name__ == '__main__':
    # 【0】set local folder
    local_folder = 'D:/Test'
    remote_folder = '/xx/Test'
    obf = UpdateFile(local_folder, remote_folder)
    try:  # 【1】 download or update
        if not os.path.exists(local_folder):  # first download
            os.mkdir(local_folder)
            obf.init_run()
        else:  # update
            obf.update_run()
    except Exception as e:  # 【2】 deal exception
        print(e)
        shutil.rmtree(local_folder)
        os.mkdir(local_folder)
        obf.init_run()
    os.startfile(local_folder + '/package/main.exe')  # 【3】launch the main.exe

这段代码实现的就是Fig 3.1中的自动更新逻辑。

3.4 打包生成AutoClient.exe

步骤同第二章。
①生成.spec,删除dist和build文件夹
pyinstaller -F AutoClient.py
②修改.spec文件,[‘AutoClient.py’] → [‘AutoClient.py’, ‘sftp_connect.py’]
③打包成AutoClient.exe
pyinstaller AutoClient.spec

3.5 测试验收

① 将AutoClient.exe和package(目录下是main.exe和version_v1.txt)上传到服务器上。
② 在本地随机路径下运行AutoClient.exe(保持联网),将会在本地D盘生成一个D:\Test的文件夹,里面包含了package子文件夹和AutoClient.exe
③(无需任何操作)等下载完成后,会弹出cmd命令窗界面,按提示输入税前base和加班费,代码会返回计算结果即税后收入。
添加图片注释,不超过 140 字(可选)
④ 更新代码,生成新的exe。

# 据相关部门规定,加班费收入也要纳税所以将加班费也计入应纳税额
inc_tax = calcu_tax(base + extra)

⑤将新的exe和version_v2.txt上传到服务器上。
⑥运行AutoClient.exe(保持联网),看程序是否会自动更新。
⑦结果展示,不出所料成功更新到v2版本,而且运行后加班费也扣了200的税╮(╯▽╰)╭
在这里插入图片描述

喜欢的朋友可以关注我的账号,不定期会更新图像质量、python办公实用相关的笔记,
知乎号:黄子的平凡生活
v信公众号:图像质量笔记

Reference

【1】pyinstaller 5.0 fails when using --onefile with a .spec file · Issue #6762 · pyinstaller/pyinstaller · GitHub
【2】Using PyInstaller — PyInstaller 6.1.0 documentation
【3】pyinstaller打包python程序(多文件) - RonyJay - 博客园 (cnblogs.com)
【4】Pyinstaller的Spec文件用法_pyinstaller spec-CSDN博客
【5】Python实现简单自动升级exe程序版本并自动运行,适合Python自动化运维。_python 自动升级-CSDN博客
【6】Python脚本生成的exe文件自动升级程序实现方法_python.exe 低版本怎么打补丁-CSDN博客
【7】用Python实现一个软件自动升级系统_python自动更新客户端-CSDN博客
【8】一文详解Paramiko安装与使用-CSDN博客

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python中,可以使用一些工具将Python代码打包成可执行的exe文件,以便在没有安装Python解释器的环境中运行。以下是一种常用的方法: 1. 使用PyInstallerPyInstaller是一个流行的Python打包工具,可以将Python代码打包成独立的可执行文件。你可以通过以下步骤来使用PyInstaller打包exe文件: - 首先,确保你已经安装了PyInstaller。可以使用以下命令进行安装:`pip install pyinstaller` - 在命令行中,进入你的Python代码所在的目录。 - 运行以下命令来生成exe文件:`pyinstaller your_script.py` - PyInstaller将会自动分析你的代码及其依赖,并生成一个独立的exe文件。 2. 使用cx_Freeze:cx_Freeze是另一个常用的Python打包工具,它可以将Python代码打包成可执行文件。以下是使用cx_Freeze打包exe文件的步骤: - 首先,确保你已经安装了cx_Freeze。可以使用以下命令进行安装:`pip install cx_Freeze` - 创建一个名为`setup.py`的文件,并在其中编写以下内容: ```python from cx_Freeze import setup, Executable setup( name="Your Program", version="0.1", description="Description of your program", executables=[Executable("your_script.py")] ) ``` - 在命令行中,进入`setup.py`所在的目录。 - 运行以下命令来生成exe文件:`python setup.py build` 这些工具可以帮助你将Python代码打包成可执行的exe文件,以便在没有Python解释器的环境中运行。你可以根据自己的需求选择适合的工具进行使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值