iT邦幫忙

0

如何使用PyInstaller打包Python代碼為執行檔-3

2020.07.02 同事作業系統升級後跟我反應程式打不開,查了一下log發現是呼叫pandas時出了問題,解決方法是把pandas移除後安裝1.0.1版本的pandas就可以了ImportError: DLL load failed while importing aggregations: The specified module could not be found

2020.12.08 這邊提醒一下,安裝版的python和免安裝版的python,版次建議一致,否則tkinter那邊會有問題

工作環境:

-Windows 10, Windows Server 2019
-Anaconda 2020.02
-Python 3.6.10

問題描述:

我們已經掌握了如何使用pyinstaller的打包功能,但如前文文末所提,不論是壓縮成一個執行檔或者不壓縮但會有一堆奇奇怪怪的庫資料夾,其檔案大小都非常肥大且開啟速度異常緩慢,這是需要改進的。下圖左為透過UPX壓縮為將所有資源都壓縮在1個執行檔,所以每次開啟.exe都需要做一次一次解壓縮動作,因此視電腦效能高低,基本上要1~5分鐘才能進到執行階段(這你敢信?);下圖右為不採UPX的檔案大小,可以看到整整肥了3倍而且資料夾中有一堆亂七八糟的東西,開啟速度也沒有比隔壁那位快多少呵呵。
比較表

本篇文100%參考自知乎網友CodingDog的pyinstaller技術文,因此這篇只是做一些畫蛇添足,主要原因是原文中不少演練的GIF都掛了,怕自己過陣子老年癡呆忘了怎麼操作所以趕緊留一份筆記。

我推薦大家使用神器Notepad++來進行本篇的所有操作。

解決方法:

開頭先講

  • 這篇講的方法沒有辦法保護你的原代碼,也就是說會被看光光的意思。當然你可以用python官方提供的py_compile或者compileall進行簡單加密:
import py_compile
py_compile.compile('C:\\Users\\*\\Downloads\\python-3.6.8-embed-amd64\\main.py')

然後自行腦補將本篇後段的.py全部改成.cpython-36.pyc就可以了。
例如test3.bat可以改寫為

@echo off 
if "%1" == "h" goto begin 
mshta vbscript:createobject("wscript.shell").run("%~nx0 h",0)(window.close)&&exit 
:begin 
python.exe test2.cpython-36.pyc
  • 不是真正意義上的純.exe,最終結果會像上圖右,但開啟速度瞬間提升100倍

前提篇

系列文首篇,主要說明有三個方法可以改善肥大問題

  • 去除不必要的库:
    我的方法與他不同,不過這也是部分網友常做的方法。是將.py檔中不必要的庫進行刪除、只匯入需要的子庫,結果是失敗。

  • 共享依赖:
    第一次看到,但沒有進行驗證。

  • 虚拟环境:
    不管是用Anaconda新建一個乾淨的環境,或者直接用另一台電腦灌官方Python,目的都在於讓pyinstaller沒有機會亂打包一些阿哩不打的庫,結果是失敗。

  • UPX压缩大法:
    我前篇文章提過了,就是把所有資源都壓縮成一個.exe檔,但頂多就是從腿庫變成滷肉(一樣肥),而且還有開啟速度緩慢的後遺症,真的母湯。

建議看一下[7]~[12]的文章,它們有更詳細的步驟紀錄,我發現我的步驟跟他們差很多,這似乎是我失敗的原因,畢竟不少人表示虚拟环境是很穩的...

惊喜篇

  1. 先到python 免安裝版本下載連結下載你需要的版本,我自己是下載3.6.8,接著解壓縮。

  2. 以系統管理員身份執行命令提示字元,輸入下列代碼來執行python.exe,不需要設定PATH。跳出方法只需要[Ctrl]+[Z],等到畫面出現^Z後按下[Enter]就可以離開Python.exe並且回到命令提示字元。

肥宅: C:\Users\*\Downloads\python-3.6.8-embed-amd64\python.exe =>確保不會開啟到Anaconda的python.exe

執行python.exe

  1. 寫一個測試用的.py檔
import os

print('IT help me!!!')

os.system('pause')

然後透過命令提示字元來進行測試這個免安裝版的python能否正常運行。

肥宅: cd C:\Users\*\Downloads\python-3.6.8-embed-amd64 =>確保可以找到test.py
肥宅: C:\Users\*\Downloads\python-3.6.8-embed-amd64\python.exe test.py

執行test.py

我很納悶為什麼不能像原作者一樣只打這行就可以執行test.py

肥宅: C:\Users\*\Downloads\python-3.6.8-embed-amd64\python.exe test.py

拓展篇

免安裝版的python是沒有pip功能的,也就是沒辦安裝庫,透過下面步驟來解鎖pip功能吧~

  1. python3*._pth的尾行代碼的註釋給取消掉,因為我是載3.6.8,所以是將python36._pth的末行代碼的註釋給取消。
    取消註釋
  1. Python官網下載get-pip.py,我把它放在資料夾python-3.6.8-embed-amd64裡面。然後再透過python.exe來執行它。
肥宅: cd C:\Users\*\Downloads\python-3.6.8-embed-amd64
肥宅: C:\Users\*\Downloads\python-3.6.8-embed-amd64\python.exe get-pip.py

執行get-pip.py

  1. 測試嚕,要像我這樣打才行哦
肥宅: C:\Users\*\Downloads\python-3.6.8-embed-amd64\Scripts\pip.exe install 你要的套件

測試pip

  1. 以防萬一,我們開啟python.exe來確認一下庫是否可以正常運作。
    測試庫

探索篇

免安裝版的python除了沒有pip功能外,它也沒有tkinter能用,你可以試著輸入下面這行代碼試試:

肥宅: cd C:\Users\*\Downloads\python-3.6.8-embed-amd64\python.exe
肥宅: import tkinter

嚇死偶了,沒想到親兒子tkinter真的不給用,據原作者爬文後發現原來是被閹割了
tkinter不能用

解決方法很簡單,從Anaconda或者官方python的資料夾中剪剪貼貼就行了:

  1. 以我為例,將下面路徑的.\anaconda3\envs\keras4\Lib\tkinter資料夾複製起來,然後扔到.\python-3.6.8-embed-amd64\Lib\site-packages
從 C:\Users\*\AppData\Local\Continuum\anaconda3\envs\keras4\Lib
複製 資料夾[tkinter]
到 C:\Users\*\Downloads\python-3.6.8-embed-amd64\Lib\site-packages
粘貼 資料夾[tkinter]

複製tkinter資料夾

  1. 再次執行import tkinter,但是發生__init__.py第36行報錯:
肥宅: cd C:\Users\*\Downloads\python-3.6.8-embed-amd64\python.exe
肥宅: import tkinter

tkinter報錯

在原作者永不放棄的精神下的追查下發現,原來是一個叫Tk的庫沒有安裝,導致tkinter無法運行:
Tk的庫沒有安裝

  1. 我們只要再去.\anaconda3\envs\keras4\DLLs偷_tkinter.pydtcl86t.dlltk86t.dll這三個檔案到.\python-3.6.8-embed-amd64就行了
C:\Users\*\AppData\Local\Continuum\anaconda3\envs\keras4\DLLs\tk86t.dll
C:\Users\*\AppData\Local\Continuum\anaconda3\envs\keras4\DLLs\tcl86t.dll
C:\Users\*\AppData\Local\Continuum\anaconda3\envs\keras4\DLLs\_tkinter.pyd
  1. 還有,把.\anaconda3\envs\keras4的tcl資料夾整個複製到.\python-3.6.8-embed-amd64

  2. 最後,我們建立一個test2.py,裡面有著從[14]尻來的tkinter測試代碼,然後用python.exe執行,試試tkinter到底復活了沒有

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author:洪卫

import tkinter as tk  # 使用Tkinter前需要先导入

# 第1步,实例化object,建立窗口window
window = tk.Tk()

# 第2步,给窗口的可视化起名字
window.title('My Window')

# 第3步,设定窗口的大小(长 * 宽)
window.geometry('500x300')  # 这里的乘是小x

# 第4步,在图形界面上设定标签
l = tk.Label(window, text='你好!this is Tkinter', bg='green', font=('Arial', 12), width=30, height=2)
# 说明: bg为背景,font为字体,width为长,height为高,这里的长和高是字符的长和高,比如height=2,就是标签有2个字符这么高

# 第5步,放置标签
l.pack()    # Label内容content区域放置位置,自动调节尺寸
# 放置lable的方法有:1)l.pack(); 2)l.place();

# 第6步,主窗口循环显示
window.mainloop()
# 注意,loop因为是循环的意思,window.mainloop就会让window不断的刷新,如果没有mainloop,就是一个静态的window,传入进去的值就不会有循环,mainloop就相当于一个很大的while循环,有个while,每点击一次就会更新一次,所以我们必须要有循环
# 所有的窗口文件都必须有类似的mainloop函数,mainloop是窗口文件的关键的关键。

# 来源: 洪卫の博客
# 作者: 洪卫
# 文章链接: https://sunhwee.com/posts/80fa3a85.html
# 本文章著作权归作者所有,任何形式的转载都请注明出处。
肥宅: python.exe test2.py

tkinter復活

補充:我在安裝openpyxl的時候,發現系統報錯,原因是說缺少一個叫作et_xmlfile的庫,這時怎麼辦呢,我抱著一樣用複製貼上大法的方式,還真的被我在.\anaconda3\envs\keras4\Lib\site-packages找到了一個叫作et_xmlfile的資料夾,結果還真的被我用尻尻大法給搞解決了嘿嘿嘿。
https://ithelp.ithome.com.tw/upload/images/20200406/20124766sckym3y7M9.png

GCC篇

實際面上,我們不可能要求用戶每次都開啟命令提示字元並且輸入代碼來執行程式

肥宅: C:\Users\*\Downloads\python-3.6.8-embed-amd64\python.exe test.py

因此有少數人把腦筋動到GCC上面[5][12],這邊就先用C語言寫個Hellow World來演練一下GCC打包吧。

  1. 首先當然是檢查自己的電腦是否已經有安裝GCC了,一樣是在命令提示字元下執行
肥宅: gcc -v

檢查GCC
如果輸出結果與紅框類似就代表你的電腦逮就補,如果完全不同,那請到[13]下載MinGW,該文章還有附MinGW的載點(官方的載點我真的看不懂怎麼下載...)

  1. 基於[5],安裝完MinGW後,執行MinGW並且僅安裝mingw32-gcc-fortanmingw32-gcc-g++,然後設定PATH:
    設定PATH

  2. 用[13]的測試代碼來體驗一下GCC的威力吧,記得要把代碼存成test.c呦(我一樣是放在資料夾python-3.6.8-embed-amd64)

#include<stdio.h>
int main()
{
printf("hello world\n");
getchar();

return 0;
}

打包.c的代碼如下:

肥宅: cd C:\Users\*\Downloads\python-3.6.8-embed-amd64=>切換路徑
肥宅: gcc test.c -o test.exe=>打包
肥宅: test.exe=>測試執行檔是否能開啟

測試test.exe

You can see see,test.exe居然只有40KB耶,如果是pyinstaller打包我保證一定遠大於這個數字,有興趣的朋友可以自己實驗看看~
檢視test.exe大小

  1. 最關鍵的地方來了,如何用GCC打包.py檔呢。我們先建立一個較new_test.c的文件檔並且放置在資料夾python-3.6.8-embed-amd64,裡面寫著:
  • 方法1. .py和.exe只需要放在同一層資料夾,至於是哪裏都沒差
#include<stdio.h>
#include<stdlib.h>

int main()
{
	system("python.exe test.py");
	return 0;
}
  • 方法2. 我看不懂跟方法3的差別,不理它了

  • 方法3. 絕對路徑-要求.py一定要放在絕對路徑資料夾中,.exe放哪都行

#include<stdio.h>
#include<stdlib.h>

int main()
{
	system("C:\\Users\\*\\Downloads\\python-3.6.8-embed-amd64\\python.exe C:\\Users\\*\\Downloads\\python-3.6.8-embed-amd64\\test.py");
	return 0;
}

須注意路徑位置是否正確,像原作者是下載3.7.3版本所以它的路徑跟我不太相同。然後呢,其實這段代碼的構造跟惊喜篇的步驟3相同,滑鼠不用往上滾了我直接貼過來吧:

肥宅: C:\Users\*\Downloads\python-3.6.8-embed-amd64\python.exe test.py

到這邊請確認一下,到目前為止,我們應該有test.py、test2.py、test.c、new_test.c這4個文件檔,如果有少麻煩回到前文再建立一次。

緊接著,我們來見證奇蹟的時刻吧,哦對了我的new_test.c是採方法1,如果怕出意外就用方法3:

肥宅: cd C:\Users\*\Downloads\python-3.6.8-embed-amd64
肥宅: gcc new_test.c -o new_test.exe
肥宅: new_test.exe

執行new_test.exe

最後,展示一下如何用.bat啟動test2.py,用意是遮蔽掉黑色視窗,首先建立一個test3.c

#include<stdio.h>
#include<stdlib.h>

int main()
{
	system("C:\\Users\\*\\Downloads\\python-3.6.8-embed-amd64\\test3.bat");
	return 0;
}

或者

#include<stdio.h>
#include<stdlib.h>

int main()
{
	system("test3.bat");
	return 0;
}

然後再建立一個test3.bat

@echo off 
if "%1" == "h" goto begin 
mshta vbscript:createobject("wscript.shell").run("%~nx0 h",0)(window.close)&&exit 
:begin 
C:\\Users\\*\\Downloads\\python-3.6.8-embed-amd64\\python.exe C:\\Users\\*\\Downloads\\python-3.6.8-embed-amd64\\test2.py

或者

@echo off 
if "%1" == "h" goto begin 
mshta vbscript:createobject("wscript.shell").run("%~nx0 h",0)(window.close)&&exit 
:begin 
python.exe test2.py

然後用gcc打包test3.c成test3.exe就可以了,之後就只需要帶著python-3.6.8-embed-amd64就能到處搞破壞了。

肥宅: gcc test3.c -o new_test.exe

最終結果如下,不管是啟動時間或者檔案大小都遠小於pyinstaller的結果(基本上啦):
最終結果

最後,記得要作成壓縮檔,因為資料夾裡的資料太零散了,所以在移動資料夾時會耗費許多不必要的時間。

下次見掰掰,啾啾~

參考資料:

[1]pyinstaller打包的exe太大?你需要嵌入式python玄学 前提篇
[2]pyinstaller打包的exe太大?你需要嵌入式python玄学 惊喜篇
[3]pyinstaller打包的exe太大?你需要嵌入式python玄学 拓展篇
[4]pyinstaller打包的exe太大?你需要嵌入式python玄学 探索篇
[5]pyinstaller打包的exe太大?你需要嵌入式python玄学 GCC篇
[6]pyinstaller打包的exe太大?你需要嵌入式python玄学 充实篇
[7]用 Pyinstaller 打包 Python 程序 + 解决打包结果过大的问题
[8]解决Python利用pyinstaller封装exe过大问题
[9]python工具pyinstaller打包生成exe文件非常大的原因分析(openpyxl)openpyxl
[10]python使用pyinstaller打包exe结果太大解决
[11]Pyinstaller打包exe太大,运行太慢
[12]巧用visual studio(VC++)解决pyinstaller打包的exe文件过大的问题(一)
[13]安裝MinGW - Minimalist GNU for Windows
[14]Python GUI之tkinter窗口视窗教程大集合(看这篇就够了)


尚未有邦友留言

立即登入留言