[python]使用pyinstaller打包python程序

1 概述

python腳本運行需要python,以及依賴的各種庫。製作Exe程序,就是將該腳本、python程序及其依賴的第三方庫打包成一個可執行文件。
這就使用到一個專用工具,Pyinstaller。該程序本身也是python下的一個程序,需要在python的環境中安裝該程序,然後進行打包。

2 安裝pyinstaller

與其他python下的程序安裝類似,詳細參考我另一篇文章:https://blog.csdn.net/kevinshift/article/details/88807868。

3 打包示例

進入要打包的環境中,輸入以下命令

(tensorflow) C:\Users\Kevin>pyinstaller -F PythonTester.py

其中-F表示只打包成一個Exe文件。
出現如下命令時表示成功。
在這裏插入圖片描述
成功後默認會生成以下文件:
(1)pyTest.exe,默認位於命令行當前路徑下的dist文件夾中。上例中位於:C:\Users\Kevin\dist 中。
(2)在C:\Users\Kevin中還會生成一個build文件夾,其中給出了該程序中間生成的一些東西。其中有一個文件提示了沒有找到的庫、被忽略的庫等信息,但這只是作爲一個參考。沒找到會被忽略了,並不一定影響最後程序的運行,還要看最後exe的結果來說。
(3)PythonTester.spec文件。在C:\Users\Kevin中生成一個spec文件,默認名稱與py文件同名,上例中爲PythonTester.spec。該文件後面會詳細說明。

4 打包生成文件形式

pyinstaller打包文件包含兩種情況:
(1)將py文件、python及第三方庫全部打包爲一個單獨的Exe中。

(2)將以上三者打包形成一個文件夾,文件夾中包含一個Exe,一個python,及其依賴的第三方庫。
二者通過不同的選項
二者的優劣對比:
(a)啓動時間
單一可執行文件比文件夾的啓動時間要長
因爲當程序運行時,單一的可執行文件需要解壓程序的第三方依賴文件到臨時文件夾中。
(b)文件結構
單一可執行文件的文件結構和工程目錄是一樣的,但是生成文件夾就不一樣了,若程序中包含相對路徑,這個相對路徑自然基於的是文件夾目錄,這點需要注意。
在打包過程出現問題時,可以生成文件結構,進入細緻查看發生了什麼。

5 pyinstaller文件如何打包

5.1從哪打包

將python文件、python程序及程序相關的第三方庫都打包。這樣該包就可以在別的地方獨立運行了。
python文件的打包是基於某個運行環境的,因此必須切到那個環境下,上例中我們在tensorflow中。而環境的第三方庫位於環境(環境位於C:\ProgramData\Anaconda3\envs)的Lib\site-packages文件夾下。所以本例中就是位於:C:\ProgramData\Anaconda3\envs\tensorflow\Lib\site-packages

5.2 找哪些文件,如何找的

打包過程中,pyinstaller不會將所有的都打包,而是將本文件運行所需要的第三方庫打包的。
當然,還有我們自己的程序運行,可能也需要一些配置文件等,有二進制文件、文本文件等形式。這些如何打包呢?
第三方庫、連同自己的文件,有幾種形式,pyinstaller按照不同方法得到:
(1)python文件。
這個是通過分析該python文件,分析其顯示import的庫,並遞歸的去尋找這些python文件,依次將其包含。
當然這裏有一個問題,對於隱式調用的則不能被包含。此時打包後的exe將會出現無法找到某些庫的情況。此時需要手動加入包含模塊,方法見spec文件的hiddenimports介紹(當然也可以命令行、等效的)。
在打包的過程中,這個是問題最多的地方,表現形式也多種多樣,但很多都是這種情況引起的,在另一篇文章中將介紹tensorflow中隱式調用的模塊的情況。
(2)二進制文件
二進制文件是無法通過上面的方法分析到的,必須通過binaries指定,告知從哪打包到哪。詳見spec文件中對於binaries的介紹。
(3)其他數據文件
跟二進制文件類似,通過datas指定,詳見spec中的datas介紹。

6 打包程序運行原理

6.1 帶文件夾的Exe運行原理

其實這個文件夾就是一個小型的環境,是之前那個打包的環境的一個子集,可以看下他的目錄結構,跟環境是很像的。
Exe中將python文件已經封裝了,其中有指定運行該文件,然後調用python和第三方庫。
其中python文件和第三方庫,自己的配置文件等都放在文件夾下。
而Exe封裝了對於python文件的邏輯和調用pthon的這個運行邏輯。
運行exe,將完成調用python程序,執行python腳本。

6.2 單獨Exe運行原理

Exe中增加了一個類似於代理或boot的程序段,運行後,他會將上面所說文件夾中的python、第三方庫、配置文件等東西解壓到一個臨時文件夾中。
解壓之後,就跟上面帶文件夾的情況類似了。
之後再調用pthon,運行python腳本。
當關閉該程序時,Exe會將臨時文件夾刪除。
默認情況下,這個臨時文件夾的路徑位於:C:\Users\KevinAppData\Local\Temp文件夾下,這個臨時文件夾名稱爲_MEIxxxxxx,其中XXXX爲一個隨機數。
注意:正常情況下,這個臨時文件夾會被刪除,但是如果exe被意外強制關閉,將無法被刪除,那麼你的c盤就會被佔用。筆者之前就是遇到該中情況,該exe被我的程序調用,後被程序強制關閉。
也可以更改這個文件夾的路徑,方法通過–runtime-tmpdir設置,或者在spec文件中runtime_tmpdir中設置(和exe同一個文件夾下,設置爲‘.’)。

7使用Spec文件打包

7.1 概況

打包時有很多控制選項,可以通過打包命令行使用。也可以通過Spec文件指定這些選項,這樣不需要每次輸入長長的選項了。效果上二者是等效的。
其實在命令行打包時,pyinstaller會自動的生成一個spec文件,保存在命令行的當前路徑下。
(1)使用spec文件

(tensorflow) C:\Users\Kevin>pyinstaller PythonTester.spec

(2)生成Spec文件

(tensorflow) C:\Users\Kevin>pyinstaller -F PythonTester.py

如運行上面的命令時,則在C:\Users\Kevin下自動生成一個名稱爲PythonTester.spec的文件,其內容如下:

# -*- mode: python ; coding: utf-8 -*-

block_cipher = None

a = Analysis(['X:\\python\\PythonTester.py'],
             pathex=['C:\\Users\\Kevin'],
             binaries=[],
             datas=[],
             hiddenimports=[],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          a.binaries,
          a.zipfiles,
          a.datas,
          [],
          name='PythonTester',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          upx_exclude=[],
          runtime_tmpdir=None,
          console=True )

7.2 基於tensorflow 2.0的python程序的一個例子

關於基於tensorflow 2.0的程序打包,有許多大坑,具體的介紹見本人的另一篇文章:https://blog.csdn.net/kevinshift/article/details/104888603

# -*- mode: python ; coding: utf-8 -*-

#下面這個設置了查找的棧深度,作者遇到了報RecursionError: maximum recursion depth exceeded情況,靠這個解決的。默認1000深度。見https://blog.csdn.net/ljt350740378/article/details/96134208
import sys
sys.setrecursionlimit(1000000) #例如這裏設置爲一百萬

block_cipher = None

a = Analysis(['X:\\\\python\\Projects\\DET.py'],
             pathex=['C:\\Users\\Kevin'],	#這是放dist的Exe等的地方
             
             #這是添加二進制文件的地方,其中第三方tensorflow的二進制文件就是靠這個解決的
             binaries=[(r'C:\ProgramData\Anaconda3\envs\tensorflow4\Lib\site-packages\tensorflow_core\lite\experimental\microfrontend\python\ops\_audio_microfrontend_op.so',r'.\tensorflow_core\lite\experimental\microfrontend\python\ops')
             ],
             datas=[],	#放用戶自己的配置文件
			
			#隱藏的N多個模塊。注意這裏不能用通配符。在hook文件中的該標籤是可以的
             hiddenimports=['pkg_resources.py2_warn','tensorflow','tensorflow_core.python','tensorflow_core.python.platform',
             		'tensorflow_core.python.platform.tf_logging','tensorflow_core.python.platform.self_check',
             		'tensorflow_core.python.platform.build_info','tensorflow_core.python.pywrap_tensorflow',
             		'tensorflow_core.python.pywrap_tensorflow_internal','tensorflow_core.python.util','tensorflow_core.python.util.deprecation',
             		'tensorflow_core.python.util.tf_export','tensorflow_core.python.util.tf_decorator','tensorflow_core.python.util.tf_stack',
             		'tensorflow_core.python.util.tf_inspect','tensorflow_core.python.util.*','tensorflow_core.python.util.decorator_utils',
             		'tensorflow_core.python.util.ANY.*','tensorflow_core.python.util.is_in_graph_mode','tensorflow_core.python.util.tf_contextlib',
             		'tensorflow_core.core','tensorflow_core.core.framework','tensorflow_core.core.framework.graph_pb2','tensorflow_core.core.framework.node_def_pb2',
             		'tensorflow_core.core.framework.attr_value_pb2','tensorflow_core.core.framework.tensor_pb2'],
             hookspath=['.'],	#hook文件存放的路徑,這裏使用了當前路徑,就是pathex=['C:\\Users\\Kevin']指定的路徑

			#下面這幾個是默認的,沒用到,暫不說了
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          a.binaries,
          a.zipfiles,
          a.datas,
          [],
          name='DEServer',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          upx_exclude=[],
          runtime_tmpdir='.',	#臨時文件夾的保存路徑,該文件指定了爲運行的exe的當前文件夾,即和exe同一個文件夾下。
          console=True )

幾個特殊說明:

7.3 Hook文件與hookpath

(1)hookspath。
指定了hook文件的路徑。hookspath告訴pyinstall到那裏去搜索這些hook文件。那麼pyinstaller在打包時,一旦遇到對應模塊有hook文件時,要執行hook文件中的操作。
(2)hook文件
hook文件是一個python文件。hook文件用於告訴pyinstall打包對應的python模塊文件時,需要執行的的hook文件中的操作。
(a)hook文件命名
hook文件與其對應的python文件的關聯是通過hook文件本身的名稱命名規則約定了。例如,如果我們要告訴pyinstall在打包tensorflow模塊時,要執行包含工作,則該hook的命名爲:hook-tensorflow.py。
(b)hook文件的內容
(i)可以時python文件任何內容。
(ii)spec文件中的內容
(iii)pyinstaller爲打包專門定義了一些庫,可以引用這些庫,做一些特殊的操作。當然這本質上還是python文件編程內容。

來兩個示例,這是本人基於tensorflow2.0 打包時用到的兩個:
(a)hook-tensorflow.py

#從pyinstaller庫中引用了模塊
from PyInstaller.utils.hooks import collect_data_files, collect_submodules

#下面用於將以下模塊下所有的子模塊全部加入
hiddenimports = collect_submodules('tensorflow_core.core.framework')
hiddenimports += collect_submodules('tensorflow_core.core')
hiddenimports += collect_submodules('tensorflow_core')
hiddenimports += collect_submodules('tensorflow_core.lite.experimental.microfrontend.python.ops')

(b)hook-tensorflow.core.framework.py

hiddenimports=['pkg_resources.py2_warn','tensorflow','tensorflow_core.python','tensorflow_core.python.platform',
             		'tensorflow_core.python.platform.tf_logging','tensorflow_core.python.platform.self_check',
             		'tensorflow_core.python.platform.build_info','tensorflow_core.python.pywrap_tensorflow',
             		'tensorflow_core.python.pywrap_tensorflow_internal','tensorflow_core.python.util','tensorflow_core.python.util.deprecation',
             		'tensorflow_core.python.util.tf_export','tensorflow_core.python.util.tf_decorator','tensorflow_core.python.util.tf_stack',
             		'tensorflow_core.python.util.tf_inspect','tensorflow_core.python.util.*','tensorflow_core.python.util.decorator_utils',
             		'tensorflow_core.python.util.ANY.*','tensorflow_core.python.util.is_in_graph_mode','tensorflow_core.python.util.tf_contextlib',
             		'tensorflow_core.core.*','tensorflow_core.core.framework.*'
             		]

類似於spec文件,但這裏是可以用通配符的:
(i)tensorflow_core.core.,表示所有子模塊
(ii)tensorflow_core.ANY.
,表示下面的任意一個所有。

8 pyinstall的幫助文檔和介紹

【1】pyinstaller官網的:
http://www.pyinstaller.org/

【2】pyinstaller官方幫助文檔:https://pyinstaller.readthedocs.io/en/stable/

https://pypi.org/project/PyInstaller/

【3】其他網友的介紹:
翻譯官方幫助文檔部分:
https://zhuanlan.zhihu.com/p/40716095
【4】其他介紹:
http://blog.itpub.net/26736162/viewspace-2644904/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章