做了一個圖形介面的小程式,想要分享給別人的話,就要把它打包成exe檔案,這樣其他人不用安裝python環境還有各種庫就可以使用了。
在打包之前,要保證你當前使用的python環境是“乾淨”的,什麼是乾淨的,為什麼要是乾淨的的呢?這是因為我們打包的時候只需要把程式中用到的包打包就行了,如果你當前使用的環境安裝的包太多的話,比如你用的是Anaconda自帶的python環境,裡面包含了大量的我們根本用不到的各種包,但是打包過程中很有可能把相關的依賴包全都搞在一起了,所以最後生成的exe檔案就會非常大,一個非常小的小程式打包完可能都會有200M多。此外,在程式碼裡面儘量不要用import,能from...import...就儘量用這個,因為如果是import的話,會將整個包都打包到exe裡面,增大了生成的exe檔案的大小。
如果你有Anaconda,為了保證我們的python環境是乾淨的,可以用conda命令新建一個環境:
conda create -n python36 python=3.6
執行完這條命令,你就擁有了一個新的python3.6環境,它在Anaconda的envs資料夾下,你需要把它新增到環境變數中去(下面的圖很直觀了)。如有你也可以建立其它版本的python環境,比如python2.7等。conda僅安裝了python 3.6相關的必須項,如python、pip等,所以新環境是非常乾淨的,我們可以根據程式需要再安裝一些必要的包就可以了。
想要更新包可以使用這個命令:
pip install --upgrade xxxxx # xxxxx改為要更新的包
搞好python環境我們就可以開始安裝pyinstaller了,這個也很簡單,但需要注意的是,你現在已經有了不止一個python環境,可以用“where python”檢視當前的所有python環境。
為了保證我們是安裝在了正確的python環境中,需要開啟python36中的“Scripts”資料夾,在此資料夾中右鍵並點選“OpenCMDHere”,這時再執行pip命令就是安裝在了這個資料夾中。
或者我們可以把該資料夾下的pip.exe複製一份並改成其他名字,如pip36.exe,這樣我們就可以使用pip36命令安裝了而不用再切換到該資料夾,因為這時候系統只能找到這一個命令,這樣就能放心地用而不用擔心安裝錯地方了。
現在我們先來安裝pywin32:
pip install pywin32
再安裝pyinstaller:
pip install pyinstaller
下面就可以打包檔案了,開啟CMD,將路徑cd到你的檔案目錄,然後:
pyinstaller -F -w myfile.py -i cat.ico
其中 -F 和 -w 是打包引數,-F 表示生成單個可執行檔案,-w 表示去掉控制檯視窗,-i 表示可執行檔案的圖示。想獲取圖示的話可以從這個網站找:https://www.easyicon.net/。
另外要注意,你要打包的程式檔案(比如myfile.py)的路徑中不要有中文。還有如果你的程式碼是存在依賴的,即多檔案的,而非所有程式碼都在一個檔案中的,不要使用-F。
通用引數
引數名 | 描述 | 說明 |
---|---|---|
-h | 顯示幫助 | 無 |
-v | 顯示版本號 | 無 |
–distpath | 生成檔案放在哪裡 | 預設:當前目錄的dist資料夾內 |
–workpath | 生成過程中的中間檔案放在哪裡 | 預設:當前目錄的build資料夾內 |
-y | 如果dist資料夾內已經存在生成檔案,則不詢問使用者,直接覆蓋 | 預設:詢問是否覆蓋 |
–upx-dir UPX_DIR | 指定upx工具的目錄 | 預設:execution path |
-a | 不包含unicode支援 | 預設:儘可能支援unicode |
–clean | 在本次編譯開始時,清空上一次編譯生成的各種檔案 | 預設:不清除 |
–log-level LEVEL | 控制編譯時pyi列印的資訊 | 一共有5個等級,由低到高分別為TRACE DEBUG INFO(預設) WARN ERROR CRITICAL。也就是預設清空下,不列印TRACE和DEBUG資訊 |
與生成結果有關的引數
引數名 | 描述 | 說明 |
---|---|---|
-D | 生成one-folder的程式(預設) | 生成結果是一個目錄,各種第三方依賴、資源和exe同時儲存在該目錄 |
-F | 生成one-file的程式 | 生成結果是一個exe檔案,所有的第三方依賴、資源和程式碼均被打包進該exe內 |
–specpath | 指定.spec檔案的儲存路徑 | 預設:當前目錄 |
-n | 生成的.exe檔案和.spec的檔名 | 預設:使用者指令碼的名稱,即main.py和main.spec |
指定打包哪些資源、程式碼
引數名 | 描述 | 說明 |
---|---|---|
–add-data | 打包額外資源 | 用法:pyinstaller main.py –add-data=src;dest。windows以;分割,linux以:分割 |
–add-binary | 打包額外的程式碼 | 用法:同–add-data。與–add-data不同的是,用binary新增的檔案,pyi會分析它引用的檔案並把它們一同新增進來 |
-p | 指定額外的import路徑,類似於使用PYTHONPATH | 參見PYTHONPATH |
–hidden-import | 打包額外py庫 | pyi在分析過程中,有些import沒有正確分析出來,執行時會報import error,這時可以使用該引數 |
–additional-hooks-dir | 指定使用者的hook目錄 | hook用法參見其他,系統hook在PyInstaller\hooks目錄下 |
–runtime-hook | 指定使用者runtime-hook | 如果設定了此引數,則runtime-hook會在執行main.py之前被執行 |
–exclude-module | 需要排除的module | pyi會分析出很多相互關聯的庫,但是某些庫對使用者來說是沒用的,可以用這個引數排除這些庫,有助於減少生成檔案的大小 |
–key | pyi會儲存位元組碼,指定加密位元組碼的key | 16位的字串 |
生成引數
引數名 | 描述 | 說明 |
---|---|---|
-d | 執行生成的main.exe時,會輸出pyi的一些log,有助於查錯 | 預設:不輸出pyi的log |
-s | 優化符號表 | 原文明確表示不建議在windows上使用 |
–noupx | 強制不使用upx | 預設:儘可能使用。 |
其他
引數名 | 描述 | 說明 |
---|---|---|
–runtime-tmpdir | 指定執行時的臨時目錄 | 預設:使用系統臨時目錄 |
Windows和Mac特有的引數
引數名 | 描述 | 說明 |
---|---|---|
-c | 顯示命令列視窗 | 與-w相反,預設含有此引數 |
-w | 不顯示命令列視窗 | 編寫GUI程式時使用此引數有用。 |
-i | 為main.exe指定圖示 | pyinstaller -i beauty.ico main.py |
Windows特有的引數
引數名 | 描述 | 說明 |
---|---|---|
–version-file | 新增版本資訊檔案 | pyinstaller –version-file ver.txt |
-m, –manifest | 新增manifest檔案 | pyinstaller -m main.manifest |
-r RESOURCE | 請參考原文 | |
–uac-admin | 請參考原文 | |
–uac-uiaccess | 請參考原文 |
執行結束後會多出兩個資料夾和一個spec檔案,生成的exe檔案就在dist資料夾中。
但是如果直接點選執行的話會出現下面的錯誤:
這是因為我的程式中呼叫了一個圖片檔案:
# 插入背景圖片
image = Image.open('python_logo.gif')
bg_img = ImageTk.PhotoImage(image)
label_img = Label(top, image=bg_img, cursor='spider')
所以我們需要把該圖片和生成的exe檔案放在一個資料夾下:
這樣exe檔案就能正常運行了:
把依賴檔案(也就是這個gif圖片)拷貝到生成應用的目錄這種方法雖然可行,但每次都要拷貝檔案未免太過麻煩,而且依賴檔案越多拷貝起來就越煩,我們希望的是生成exe檔案的同時就把這些圖片等依賴檔案全部都打包。這時,我們可以通過操作spec檔案解決這個問題。
首先,在對應的檔案目錄下開啟CMD,輸入如下命令即可生成對應的spec檔案:
pyi-makespec xxxxx.py
生成的spec檔案的內容如下:
# -*- mode: python -*-
block_cipher = None
a = Analysis(['crawl_video.py'],
pathex=['E:\\PycharmProjects\\zhihu_video'],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
exclude_binaries=True,
name='crawl_video',
debug=False,
strip=False,
upx=True,
console=True )
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
name='crawl_video')
spec檔案中主要包含4個class: Analysis, PYZ, EXE和COLLECT
-
Analysis以py檔案為輸入,它會分析py檔案的依賴模組,並生成相應的資訊
-
PYZ是一個.pyz的壓縮包,包含程式執行需要的所有依賴
-
EXE根據上面兩項生成
-
COLLECT生成其他部分的輸出資料夾,COLLECT也可以沒有
我們上面說過,有時候pyInstaller自動生成的spec檔案並不能滿足我們的需求,最常見的情況就是我們的程式依賴我們本地的一些資料檔案,這個時候就需要我們自己去編輯spec檔案來新增資料檔案了。
上面的spec檔案解析中Analysis中的datas就是要新增到專案中的資料檔案,我們可以編輯datas:
a = Analysis(
...
datas = [('you/source/file/path','file_name_in_project'),
('source/file2', 'file_name2')]
...
)
可以認為datas是一個List,每個元素是一個二元組。元組的第一個元素是你本地檔案索引,第二個元素是拷貝到專案中之後的檔名字。將其寫作[(a,b)],a表示依賴所在的資料夾路徑或者檔案路徑,如果a表示資料夾路徑,那麼資料夾下面的所有檔案都會被拷貝到應用資料夾;b表示應用資料夾下的目標路徑,b引數空缺表示依賴檔案全部拷貝到根目錄下。如果要拷貝到特定的目錄下,那麼需要寫作‘.\\icon’,icon就是資料夾的名字(當然可以換成其他名字)。
除了上面那種寫法,也可以將其提出來。
added_files = [...]
a = Analysis(
...
datas = added_files,
...
)
比如我們想把一個圖片新增進去,就要修改datas引數:
datas=[('E:\\PycharmProjects\\zhihu_video\\python_logo.gif','')]
注意,如果你不想生成帶命令列視窗的應用,還需要把“console”引數的值改為False;如果想要給應用更換圖示的話,需要新增icon引數,COLLECT部分也可以去掉:
# -*- mode: python -*-
block_cipher = None
a = Analysis(['crawl_video.py'],
pathex=['E:\\PycharmProjects\\zhihu_video'],
binaries=[],
datas=[('E:\\PycharmProjects\\zhihu_video\\python_logo.gif', '')],
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
name='crawl_video',
debug=False,
strip=False,
upx=True,
runtime_tmpdir=None,
console=False , icon='mimi.ico')
編輯完儲存,再執行如下語句即可生成應用:
pyinstaller xxxxx.spec
但是...雖然生成了exe檔案,但還是會報錯,仍然需要把依賴圖片拷貝到和exe相同的資料夾下:
WTF。。。我也不知道這是為什麼!
找了半天,終於在一個貼吧裡找到了一個解決方案,解決方法就是對圖片檔案進行編碼轉換成py檔案,然後就能一起打包了,而且不需要操作spec檔案:
# pic_to_py.py
import base64
def gif_to_py(picture_name):
open_gif = open("%s.gif" % picture_name, 'rb')
b64str = base64.b64encode(open_gif.read())
open_gif.close()
write_data = 'img = "%s"' % b64str.decode()
f = open('%s.py' % picture_name, 'w+')
f.write(write_data)
f.close()
if __name__ == '__main__':
picture = ['python_logo']
for picture_position in picture:
gif_to_py(picture_position)
我轉換的是gif圖片,如果需要轉換別的型別的圖片自己在程式裡修改就行了。
然後在你的tkinter檔案內匯入你生成的py檔案:
import base64
from pictures.python_logo import img as logo
tmp = open('tmp.gif', 'wb+') # 臨時檔案用來儲存gif檔案
tmp.write(base64.b64decode(logo))
tmp.close()
image = Image.open('tmp.gif')
bg_img = ImageTk.PhotoImage(image)
label_img = Label(top, image=bg_img, cursor='spider')
os.remove('tmp.gif')
接著使用pyinstaller正常操作,不需要對spec檔案作出修改,生成的exe檔案就能正常使用了:
pyinstaller -F -w crawl_video.py -i mimi.ico
留言列表