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/