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