zipapp
—— 管理可執(zhí)行的 Python zip 打包文件?
3.5 新版功能.
源代碼: Lib/zipapp.py
本模塊提供了一套管理工具,用于創(chuàng)建包含 Python 代碼的壓縮文件,這些文件可以 直接由 Python 解釋器執(zhí)行。 本模塊提供 命令行接口 和 Python API。
簡單示例?
下述例子展示了用 命令行接口 根據(jù)含有 Python 代碼的目錄創(chuàng)建一個可執(zhí)行的打包文件。 運行后該打包文件時,將會執(zhí)行 myapp
模塊中的 main
函數(shù)。
$ python -m zipapp myapp -m "myapp:main"
$ python myapp.pyz
<output from myapp>
命令行接口?
若要從命令行調(diào)用,則采用以下形式:
$ python -m zipapp source [options]
如果 source 是個目錄,將根據(jù) source 的內(nèi)容創(chuàng)建一個打包文件。如果 source 是個文件,則應為一個打包文件,將會復制到目標打包文件中(如果指定了 -info 選項,將會顯示 shebang 行的內(nèi)容)。
可以接受以下參數(shù):
- -o <output>, --output=<output>?
將程序的輸出寫入名為 output 的文件中。若未指定此參數(shù),輸出的文件名將與輸入的 source 相同,并添加擴展名
.pyz
。如果顯式給出了文件名,將會原樣使用(因此必要時應包含擴展名.pyz
)。如果 source 是個打包文件,必須指定一個輸出文件名(這時 output 必須與 source 不同)。
- -p <interpreter>, --python=<interpreter>?
給打包文件加入
#!
行,以便指定 解釋器 作為運行的命令行。另外,還讓打包文件在 POSIX 平臺上可執(zhí)行。默認不會寫入#!
行,也不讓文件可執(zhí)行。
- -m <mainfn>, --main=<mainfn>?
在打包文件中寫入一個
__main__.py
文件,用于執(zhí)行 mainfn。mainfn 參數(shù)的形式應為 “pkg.mod:fn”,其中 “pkg.mod”是打包文件中的某個包/模塊,“fn”是該模塊中的一個可調(diào)用對象。__main__.py
文件將會執(zhí)行該可調(diào)用對象。在復制打包文件時,不能設置
--main
參數(shù)。
- -c, --compress?
利用 deflate 方法壓縮文件,減少輸出文件的大小。默認情況下,打包文件中的文件是不壓縮的。
在復制打包文件時,
--compress
無效。3.7 新版功能.
- --info?
顯示嵌入在打包文件中的解釋器程序,以便診斷問題。這時會忽略其他所有參數(shù),SOURCE 必須是個打包文件,而不是目錄。
- -h, --help?
打印簡短的用法信息并退出。
Python API?
該模塊定義了兩個快捷函數(shù):
- zipapp.create_archive(source, target=None, interpreter=None, main=None, filter=None, compressed=False)?
由 source 創(chuàng)建一個應用程序打包文件。source 可以是以下形式之一:
一個目錄名,或指向目錄的 path-like object ,這時將根據(jù)目錄內(nèi)容新建一個應用程序打包文件。
一個已存在的應用程序打包文件名,或指向這類文件的 path-like object,這時會將該文件復制為目標文件(會稍作修改以反映出 interpreter 參數(shù)的值)。必要時文件名中應包括
.pyz
擴展名。一個以字節(jié)串模式打開的文件對象。該文件的內(nèi)容應為應用程序打包文件,且假定文件對象定位于打包文件的初始位置。
target 參數(shù)定義了打包文件的寫入位置:
若是個文件名,或是 path-like object,打包文件將寫入該文件中。
若是個打開的文件對象,打包文件將寫入該對象,該文件對象必須在字節(jié)串寫入模式下打開。
如果省略了 target (或為
None
),則 source 必須為一個目錄,target 將是與 source 同名的文件,并加上.pyz
擴展名。
參數(shù) interpreter 指定了 Python 解釋器程序名,用于執(zhí)行打包文件。這將以 “釋伴(shebang)”行的形式寫入打包文件的頭部。在 POSIX 平臺上,操作系統(tǒng)會進行解釋,而在 Windows 平臺則會由 Python 啟動器進行處理。省略 interpreter 參數(shù)則不會寫入釋伴行。如果指定了解釋器,且目標為文件名,則會設置目標文件的可執(zhí)行屬性位。
參數(shù) main 指定某個可調(diào)用程序的名稱,用作打包文件的主程序。僅當 source 為目錄且不含
__main__.py
文件時,才能指定該參數(shù)。main 參數(shù)應采用 “pkg.module:callable”的形式,通過導入“pkg.module”并不帶參數(shù)地執(zhí)行給出的可調(diào)用對象,即可執(zhí)行打包文件。如果 source 是目錄且不含``__main__.py`` 文件,省略 main 將會出錯,生成的打包文件將無法執(zhí)行。可選參數(shù) filter 指定了回調(diào)函數(shù),將傳給代表被添加文件路徑的 Path 對象(相對于源目錄)。如若文件需要加入打包文件,則回調(diào)函數(shù)應返回
True
。可選參數(shù) compressed 指定是否要壓縮打包文件。若設為
True
,則打包中的文件將用 deflate 方法進行壓縮;否則就不會壓縮。本參數(shù)在復制現(xiàn)有打包文件時無效。若 source 或 target 指定的是文件對象,則調(diào)用者有責任在調(diào)用 create_archive 之后關閉這些文件對象。
當復制已有的打包文件時,提供的文件對象只需
read
和readline
方法,或write
方法。當由目錄創(chuàng)建打包文件時,若目標為文件對象,將會將其傳給 類,且必須提供zipfile.ZipFile
類所需的方法。3.7 新版功能: 加入了 filter 和 compressed 參數(shù)。
例子?
將目錄打包成一個文件并運行它。
$ python -m zipapp myapp
$ python myapp.pyz
<output from myapp>
同樣還可用 create_archive()
函數(shù)完成:
>>> import zipapp
>>> zipapp.create_archive('myapp', 'myapp.pyz')
要讓應用程序能在 POSIX 平臺上直接執(zhí)行,需要指定所用的解釋器。
$ python -m zipapp myapp -p "/usr/bin/env python"
$ ./myapp.pyz
<output from myapp>
若要替換已有打包文件中的釋伴行,請用 create_archive()
函數(shù)另建一個修改好的打包文件:
>>> import zipapp
>>> zipapp.create_archive('old_archive.pyz', 'new_archive.pyz', '/usr/bin/python3')
若要原地更新打包文件,可用 BytesIO
對象在內(nèi)存中進行替換,然后再覆蓋源文件。注意,原地覆蓋文件會有風險,出錯時會丟失原文件。這里沒有考慮出錯情況,但生產(chǎn)代碼則應進行處理。另外,這種方案僅當內(nèi)存足以容納打包文件時才有意義:
>>> import zipapp
>>> import io
>>> temp = io.BytesIO()
>>> zipapp.create_archive('myapp.pyz', temp, '/usr/bin/python2')
>>> with open('myapp.pyz', 'wb') as f:
>>> f.write(temp.getvalue())
指定解釋器程序?
注意,如果指定了解釋器程序再發(fā)布應用程序打包文件,需要確保所用到的解釋器是可移植的。Windows 的 Python 啟動器支持大多數(shù)常見的 POSIX #!
行,但還需要考慮一些其他問題。
如果采用“/usr/bin/env python”(或其他格式的 python 調(diào)用命令,比如“/usr/bin/python”),需要考慮默認版本既可能是 Python 2 又可能是 Python 3,應讓代碼在兩個版本下均能正常運行。
如果用到的 Python 版本明確,如“/usr/bin/env python3”,則沒有該版本的用戶將無法運行應用程序。(如果代碼不兼容 Python 2,可能正該如此)。
因為無法指定“python X.Y以上版本”,所以應小心“/usr/bin/env python3.4”這種精確版本的指定方式,因為對于 Python 3.5 的用戶就得修改釋伴行,比如:
通常應該用“/usr/bin/env python2”或“/usr/bin/env python3”的格式,具體根據(jù)代碼適用于 Python 2 還是 3 而定。
用 zipapp 創(chuàng)建獨立運行的應用程序?
利用 zipapp
模塊可以創(chuàng)建獨立運行的 Python 程序,以便向最終用戶發(fā)布,僅需在系統(tǒng)中裝有合適版本的 Python 即可運行。操作的關鍵就是把應用程序代碼和所有依賴項一起放入打包文件中。
創(chuàng)建獨立運行打包文件的步驟如下:
照常在某個目錄中創(chuàng)建應用程序,于是會有一個
myapp
目錄,里面有個``__main__.py`` 文件,以及所有支持性代碼。用 pip 將應用程序的所有依賴項裝入
myapp
目錄。$ python -m pip install -r requirements.txt --target myapp
(這里假定在
requirements.txt
文件中列出了項目所需的依賴項,也可以在 pip 命令行中列出依賴項)。pip 在
myapp
中創(chuàng)建的.dist-info
目錄,是可以刪除的。這些目錄保存了 pip 用于管理包的元數(shù)據(jù),由于接下來不會再用到 pip,所以不是必須存在,當然留下來也不會有什么壞處。用以下命令打包:
$ python -m zipapp -p "interpreter" myapp
這會生成一個獨立的可執(zhí)行文件,可在任何裝有合適解釋器的機器上運行。詳情參見 指定解釋器程序??梢詥蝹€文件的形式分發(fā)給用戶。
在 Unix 系統(tǒng)中,myapp.pyz
文件將以原有文件名執(zhí)行。如果喜歡 “普通”的命令名,可以重命名該文件,去掉擴展名 .pyz
。在 Windows 系統(tǒng)中,myapp.pyz[w]
是可執(zhí)行文件,因為 Python 解釋器在安裝時注冊了擴展名``.pyz`` 和 .pyzw
。
制作 Windows 可執(zhí)行文件?
在 Windows 系統(tǒng)中,可能沒有注冊擴展名 .pyz
,另外有些場合無法“透明”地識別已注冊的擴展(最簡單的例子是,subprocess.run(['myapp'])
就找不到——需要明確指定擴展名)。
因此,在 Windows 系統(tǒng)中,通常最好 由zipapp 創(chuàng)建一個可執(zhí)行文件。雖然需要用到 C 編譯器,但還是相對容易做到的?;咀龇ㄓ匈囉谝韵率聦?,即 zip 文件內(nèi)可預置任意數(shù)據(jù),Windows 的 exe 文件也可以附帶任意數(shù)據(jù)。因此,創(chuàng)建一個合適的啟動程序并將 .pyz
文件附在后面,最后就能得到一個單文件的可執(zhí)行文件,可運行 Python 應用程序。
合適的啟動程序可以簡單如下:
#define Py_LIMITED_API 1
#include "Python.h"
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#ifdef WINDOWS
int WINAPI wWinMain(
HINSTANCE hInstance, /* handle to current instance */
HINSTANCE hPrevInstance, /* handle to previous instance */
LPWSTR lpCmdLine, /* pointer to command line */
int nCmdShow /* show state of window */
)
#else
int wmain()
#endif
{
wchar_t **myargv = _alloca((__argc + 1) * sizeof(wchar_t*));
myargv[0] = __wargv[0];
memcpy(myargv + 1, __wargv, __argc * sizeof(wchar_t *));
return Py_Main(__argc+1, myargv);
}
若已定義了預處理器符號 WINDOWS
,上述代碼將會生成一個 GUI 可執(zhí)行文件。若未定義則生成一個可執(zhí)行的控制臺文件。
直接使用標準的 MSVC 命令行工具,或利用 distutils 知道如何編譯 Python 源代碼,即可編譯可執(zhí)行文件:
>>> from distutils.ccompiler import new_compiler
>>> import distutils.sysconfig
>>> import sys
>>> import os
>>> from pathlib import Path
>>> def compile(src):
>>> src = Path(src)
>>> cc = new_compiler()
>>> exe = src.stem
>>> cc.add_include_dir(distutils.sysconfig.get_python_inc())
>>> cc.add_library_dir(os.path.join(sys.base_exec_prefix, 'libs'))
>>> # First the CLI executable
>>> objs = cc.compile([str(src)])
>>> cc.link_executable(objs, exe)
>>> # Now the GUI executable
>>> cc.define_macro('WINDOWS')
>>> objs = cc.compile([str(src)])
>>> cc.link_executable(objs, exe + 'w')
>>> if __name__ == "__main__":
>>> compile("zastub.c")
生成的啟動程序用到了 “受限 ABI”,所以可在任意版本的 Python 3.x 中運行。只要用戶的 PATH
中包含了 Python(python3.dll
)路徑即可。
若要得到完全獨立運行的發(fā)行版程序,可將附有應用程序的啟動程序,與“內(nèi)嵌版” Python 打包在一起即可。這樣在架構匹配(32位或64位)的任一 PC 上都能運行。
注意事項?
要將應用程序打包為單個文件,存在一些限制。大多數(shù)情況下,無需對應用程序進行重大修改即可解決。
如果應用程序依賴某個帶有 C 擴展的包,則此程序包無法由打包文件運行(這是操作系統(tǒng)的限制,因為可執(zhí)行代碼必須存在于文件系統(tǒng)中,操作系統(tǒng)才能加載)。這時可去除打包文件中的依賴關系,然后要求用戶事先安裝好該程序包,或者與打包文件一起發(fā)布并在
__main__.py
中增加代碼,將未打包模塊的目錄加入sys.path
中。采用增加代碼方式時,一定要為目標架構提供合適的二進制文件(可能還需在運行時根據(jù)用戶的機器選擇正確的版本加入sys.path
)。若要如上所述發(fā)布一個 Windows 可執(zhí)行文件,就得確保用戶在 PATH 中包含``python3.dll`` 的路徑(安裝程序默認不會如此),或者應把應用程序與內(nèi)嵌版 Python 一起打包。
上述給出的啟動程序采用了 Python 嵌入 API。 這意味著應用程序?qū)?
sys.executable
,而*不是*傳統(tǒng)的 Python 解釋器。代碼及依賴項需做好準備。例如,如果應用程序用到了multiprocessing
模塊,就需要調(diào)用multiprocessing.set_executable()
來讓模塊知道標準 Python 解釋器的位置。
Python 打包應用程序的格式?
自 2.6 版開始,Python 即能夠執(zhí)行包含 文件的打包文件了。為了能被 Python 執(zhí)行,應用程序的打包文件必須為包含 __main__.py
文件的標準 zip 文件,__main__.py
文件將作為應用程序的入口運行。類似于常規(guī)的 Python 腳本,父級(這里指打包文件)將放入 sys.path
,因此可從打包文件中導入更多的模塊。
zip 文件格式允許在文件中預置任意數(shù)據(jù)。利用這種能力,zip 應用程序格式在文件中預置了一個標準的 POSIX “釋伴”行(#!/path/to/interpreter
)。
因此,Python zip 應用程序的格式會如下所示:
可選的釋伴行,包含字符
b'#!'
,后面是解釋器名,然后是換行符 (b'\n'
)。 解釋器名可為操作系統(tǒng) “釋伴”處理所能接受的任意值,或為 Windows 系統(tǒng)中的 Python 啟動程序。解釋器名在 Windows 中應用 UTF-8 編碼,在 POSIX 中則用sys.getfilesystemencoding()
。標準的打包文件由
zipfile
模塊生成。其中 必須 包含一個名為``__main__.py`` 的文件(必須位于打包文件的“根”目錄——不能位于某個子目錄中)。打包文件中的數(shù)據(jù)可以是壓縮或未壓縮的。
如果應用程序的打包文件帶有釋伴行,則在 POSIX 系統(tǒng)中可能需要啟用可執(zhí)行屬性,以允許直接執(zhí)行。
不一定非要用本模塊中的工具創(chuàng)建應用程序打包文件,本模塊只是提供了便捷方案,上述格式的打包文件可用任何方式創(chuàng)建,均可被 Python 接受。