PyInstaller 系列 - 基本用法


【原文地址:https://shuhari.dev/blog/2018/6/pyinstaller-introduction】

 

這是本系列的第一篇,介紹 PyInstaller 的基礎知識。

PyInstaller 是一個跨平台的 Python 應用打包工具,支持 Windows/Linux/MacOS 三大主流平台,能夠把 Python 腳本及其所在的 Python 解釋器打包成可執行文件,從而允許最終用戶在無需安裝 Python 的情況下執行你的程序。

PyInstaller 這個軟件有幾點容易引起誤解的地方。首先,Installer 的名字可能會讓用戶望文生義地覺得這是個安裝程序制作軟件,類似 NSIS/WIX/InstallShield 等等,但實際上 PyInstaller 的作用是打包而不是安裝。這可能是因為類似 py2exe 這樣的好名字早就被其他項目用掉了吧。其次,PyInstaller 制作出來的執行文件並不是跨平台的。如果你需要分別為三大平台打包的話,那么你就要在三個平台上分別運行 PyInstaller,不要指望“一次編譯,到處運行”(當然可以利用虛擬機簡化多環境配置)。

介紹 PyInstaller 的資料在網上並不少,但大多是一些簡單的運行和命令行說明,缺乏較為深入和系統性的講解。同時,許多同學使用 PyInstaller 的過程中會遇到各種各樣的問題,如果你不明白它的運行原理的話,有些問題就很難靠自己解決。這也是我編寫本系列文章的原因。

本文系列中的實踐部分,如無特別說明的話,均以 Windows 為系統平台————這應該也是大多數 PyInstaller 使用的環境。我自己的機器是 Windows 10,如果你用的是 Windows 7/8,也沒有關系,PyInstaller 在 Windows 系列上的用法沒什么不同。Mac 是另外一個較大的平台,但本文不會涉及,有問題的同學可以參考官方手冊中針對 Mac 平台的特別說明。此外,現在已經是 2018 年,故本文不會考慮 Python 2.x 系列的兼容問題。

接下來介紹 PyInstaller 的安裝和運行。

安裝 PyInstaller

Python 3 以上版本安裝 PyInstaller 最方便的方法當然是通過 PIP。直接運行如下命令即可:

pip install PyInstaller

然后等待安裝結束。PyInstaller 會安裝一些相關的庫,例如在 Windows 下會同時安裝 pypiWin32(pywin32的更新版本)。

可以用如下命令確認 PyInstaller 安裝成功:

pyinstaller --version
> 3.3.1 

運行 PyInstaller

PyInstaller 最簡單的運行形式,只需要指定作為程序入口的腳本文件。假定這個文件叫做 main.py:

pyinstaller main.py

運行之后,我們會看到目錄中多了這些內容:

  • main.spec 文件,其前綴和腳本名相同。它本質上是一個 Python 文件,指定了打包時所需的各種參數,熟悉格式以后也可以手工修改,其角色類似於 setup.py;
  • build 子目錄,其中存放打包過程中生成的臨時文件。有幾個文件需要關注一下:
  • warnxxxx.txt:該文件記錄了生成過程中的警告/錯誤信息。在一切正常的情況下,該文件也可能包含若干類似 missing module 的信息,這些信息並不表示出錯,但如果 PyInstaller 運行有問題的話,你就需要檢查這個文件來獲取錯誤的詳細內容了。
  • xref-xxxx.html:該文件輸出了 PyInstaller 分析腳本得到的模塊依賴關系圖。如果出現類似找不到模塊的信息,你可以查找這個文件來獲得一些線索。但該文件的內容比較龐大,一般情況下很少會直接使用它。
  • dist 子目錄,存放生成的最終文件。如果使用單文件模式————后面會講到),那么這里將只有單個執行文件;如果使用目錄模式的話,那么這里還會有一個和腳本同名的子目錄,其下才是真正的可執行文件(以及相關的其他附屬文件)。雙擊可執行文件就會啟動程序。

Dist 目錄內容

以上是在我機器上生成的目錄內容(部分)。可以發現,對於基本的應用程序來說,Python 解釋器本體(pythonxx.dll)是其中的大頭。當然程序文件(main.exe)體積也不小,不過同學們也不要被嚇怕了——占用空間的都是一些恆定的 Bootstrapper 內容。當你繼續增加代碼時,文件體積只會以 KB 為單位緩慢增加,不會再有如此恐怖的增長了。此外就是一些操作系統相關的輔助文件(這里是 Windows 的 UCRT 庫)。

PyInstaller 命令行選項

在上面的例子里,我們用最簡單的方式使用了 PyInstaller。實際上 PyInstaller 有相當多的命令行開關,可以從各個方面調整打包過程的行為。用如下命令就能看到所有支持的開關項:

pyinstaller --help

由於可用的選項如此之多,這里並不會逐個講解,只主要介紹經常使用的幾個開關。其他選項的詳細內容可以參考官方手冊。

  • -y | --noconfirm 直接覆蓋輸出文件,而無需提示。在多次重復運行命令時可避免煩人的反復確認。

  • -D | --onedir 生成包含執行文件的目錄(這是默認行為)

  • -F | --onefile 和上一個選項相對,生成單一的可執行文件。注意,這個選項會帶來一些特殊的行為,並且我個人強烈地不推薦你使用這個選項。具體的原因我們在后面的文章中討論。

  • -i | --icon [.ico | .exe | .icns] 為 Windows/Mac 平台的執行文件指定圖標。如果是 .exe 的話,還可以在后面加上 ,id 參數來指定具體的圖標ID。

  • --version-file [filename] 添加文件版本信息。后面我們會通過一個具體的例子說明其用法。

  • -c | --console | --nowindowed 通過控制台窗口運行程序 並且分配標准輸入/輸出,(默認行為)。

  • -w | --windowed | --noconsole 和上一個選項相反,不創建控制台窗口,也不分配標准輸入/輸出。它們主要用來運行 GUI 程序。需要說明的是,沒有輸入輸出會給調試帶來一定困難,因此即便你編寫的是 GUI 程序,也建議在調試時禁用這個選項,在最終發布時再打開。

  • --add-data [file:dir] 添加數據文件。如果有多個文件需要添加的話,該選項可以出現多次。注意,文件參數的格式為文件名+輸出目錄名,用路徑分隔符分割。路徑分割符,從技術上來講就是 os.pathsep,在 Windows 下使用 ;,其他系統下則使用 :。 如果輸出到和腳本相同的目錄,則使用 . 作為輸出目錄。

  • --add-binary [file:dir] 添加二進制文件,即運行程序所需的 .exe/.dll/.so 等。其選項格式和 --add-data 相同,不再贅述。

通過 Shell 腳本/批處理運行 PyInstaller

看到上述這么多命令,你應該可以想象,通過命令行輸入這么多參數應該是一件多么痛苦的事情。這個問題有兩個解決辦法。第一,我們可以把需要的命令保存成 Shell/批處理腳本。第二,也可以通過運行 .spec 文件來達到同樣的效果。目前,我們還沒有介紹 .spec 文件的內容,作為示例,我們先使用第一個方法。

為了避免命令行過長,在腳本中最好是使用換行符。類 Unix 系統和 Windows 系統的換行符是不同的(此外也要注意路徑分隔符的寫法)。Linux 中一個腳本的例子大概是這樣的:

pyinstaller --noconfirm --onedir --windowed \ --add-data="README.txt:." \ --add-data="sample.png:img" \ --add-binary="mylib.dll:lib" \ --icon="main.ico" \ --version-file="version.txt" \ main.py 

而 Windows 版本則是這樣:

pyinstaller --noconfirm --onedir --windowed ^  --add-data="README.txt;." ^  --add-data="sample.png;img" ^  --add-binary="mylib.dll;lib" ^  --icon="main.ico" ^  --version-file="version.txt" ^  main.py 

使用 UPX 優化執行文件大小

有不少用戶會覺得 PyInstaller 生成的文件太大,不便於發布。由於 Python 解釋器本身的體積很難裁剪,所以 PyInstaller 提供了一個方案。如果 PyIntaller 在系統路徑上發現了 UPX,那么它會用 UPX 來壓縮執行文件。如果你確實有 UPX,但又並不希望裁剪的話,可以用--no-upx 開關來屏蔽它。

盡管 UPX 對大多數程序都能有不錯的壓縮率,但作為代價,使用 UPX 以后程序執行速度可能會有所減慢。此外,個別程序也存在不兼容 UPX 的情況,因此請你自己權衡是否值得使用。

為執行文件添加版本信息

上述大多數命令行選項都是相當直觀的,相信讀者參考腳本的例子就能看懂,不需要多作解釋。但有一個選項有點復雜,值得拿出來說一下,那就是 --version-file

在 Windows 下,為應用程序添加版本信息是一種專業的表現,並且對軟件的排錯和更新也可能會有所幫助。但版本信息是一個復雜的結構————技術性的介紹可參考MSDN(https://msdn.microsoft.com/en-us/library/ff468916(v=vs.85).aspx )————自己生成版本記錄文件是比較困難的。PyInstaller 考慮到了這一點,因此它為你提供了一個用於提取版本記錄的輔助命令:pyi_grab_version。如果你的 PyInstaller 是通過 PIP 安裝的,那么該程序應該位於 Pythonxx\Scripts 子目錄下,並且如果 Python 已經添加到系統 PATH 的話,你可以直接運行此命令:

pyi-grab_version

注意中划線和下划線的寫法,如果找不到命令的話,請檢查一下 Scripts 子目錄。

這里作為例子,我們直接從系統程序里“偷”一個版本信息過來:

pyi-grab_version c:\windows\notepad.exe version.txt 

然后打開生成的 version.txt, 會看到類似下面的內容:

# UTF-8
VSVersionInfo(
  ffi=FixedFileInfo(
    # filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4)
    # Set not needed items to zero 0.
    filevers=(10, 0, 17134, 1),
    prodvers=(10, 0, 17134, 1),
  ...
  kids=[
    StringFileInfo(
      [
      StringTable(
        u'040904B0',
        [StringStruct(u'CompanyName', u'Microsoft Corporation'),
        StringStruct(u'FileDescription', u'Notepad'),
        StringStruct(u'FileVersion', u'10.0.17134.1 (WinBuild.160101.0800)'),
        StringStruct(u'InternalName', u'Notepad'),
        StringStruct(u'LegalCopyright', u'© Microsoft Corporation. All rights reserved.'),
        StringStruct(u'OriginalFilename', u'NOTEPAD.EXE'),
        StringStruct(u'ProductName', u'Microsoft® Windows® Operating System'),
        StringStruct(u'ProductVersion', u'10.0.17134.1')])
      ]), 
    VarFileInfo([VarStruct(u'Translation', [1033, 1200])])
  ]
)

文件略長,這里只列出重要的部分,其結構還是很清楚的。通常,我們需要修改的是其中的版本信息和一些輔助性的描述。將文中內容修改如下:

# UTF-8
VSVersionInfo(
  ffi=FixedFileInfo(
    filevers=(1, 0, 1, 1),
    prodvers=(1, 0, 1, 1),
    ...
    ),
  kids=[
    StringFileInfo(
      [
      StringTable(
        u'040904B0',
        [StringStruct(u'CompanyName', u'Sample Corp'),
        StringStruct(u'FileDescription', u'PyInstaller application'),
        StringStruct(u'FileVersion', u'1.0.0.1'),
        StringStruct(u'InternalName', u'PyInstaller application'),
        StringStruct(u'LegalCopyright', u'Sample Corp.'),
        StringStruct(u'OriginalFilename', u'main.exe'),
        StringStruct(u'ProductName', u'PyInstaller application'),
        StringStruct(u'ProductVersion', u'1.0.0.1')])
      ]), 
    VarFileInfo([VarStruct(u'Translation', [1033, 1200])])
  ]
)

然后編譯程序。從資源管理器找到發布目錄下的文件(本例中應為 dist/main/main.exe),右鍵查看屬性:

.EXE 版本信息

可見版本信息已經成功添加到 .EXE 文件中了。

在下一篇文章中,我們將介紹什么是單文件/單目錄模式,以及它們之間的區別。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM