close

做了一個圖形介面的小程式,想要分享給別人的話,就要把它打包成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

arrow
arrow

    低階ㄇㄋ 發表在 痞客邦 留言(0) 人氣()