2021年,你應(yīng)該知道的Python打包指南

作者:frostming
原題:Python打包指南2021 來源:https://frostming.com/2020/12-25/python-packaging
大家好,雕蟲小技欄目又和大家見面了,誰讓咱不會那些個屠龍之技,只好捉幾個蟲子玩玩了。寫這篇文章是因?yàn)檫^去的兩年關(guān)于pip和 Python 包管理有幾個重要的 PEP 發(fā)布,然而網(wǎng)上(中文世界)的打包發(fā)布教程很少有針對此的更新。再加上我成為 PyPA 的成員已經(jīng)尸位素餐快一年了,還是應(yīng)該來做點(diǎn)貢獻(xiàn)。
setup.py 真難寫
似乎從有 Python 打包以來就有了setuptools這個庫,你能搜到的教程,涉及打包發(fā)布的,都會讓你編寫那個可怕的setup.py。不知道誰能完全掌握那個東西的寫法,我到現(xiàn)在都還不太會。說幾個常用的配置:
指定依賴和可選依賴
setup(
??install_requires=["flask",?"flask-migrate",?"sqlalchemy"],
??extras_require={"mysql":?["mysqlclient"],?"pgsql":?["psycopg2"]}
)注意那兩個 key 分別是
install_requires和extras_require,別寫錯了。此外,如果你需要根據(jù)條件增減依賴的話,不要用INSTALL_REQUIRES?=?["flask"]
if?sys.platform?==?"win32":
??INSTALL_REQUIRES.append("pywin32")
setup(install_requires=INSTALL_REQUIRES)而應(yīng)該使用Environment Markers
INSTALL_REQUIRES?=?[
??"flask",
??"pywin32;?sys_platform?==?'win32'"
]
setup(install_requires=INSTALL_REQUIRES)發(fā)布可執(zhí)行程序到
/bin下setup(
??entry_points={
??????"console_scripts":?["mybin=mypackage.main:cli"]
??}
)或者 ini 寫法
setup(
??entry_points="""[console_scripts]
??mybin?=?mypackage.main:cli
??"""
)任選其一。
包含 data 文件
setup(
??include_package_data=True????#?從MANIFEST.in中讀取配置
)或者
setup(
??package_data={"":?["*.json"]}??#?包含所有json文件
)指定源代碼結(jié)構(gòu),如果你使用的是
src/存放包的源碼這種項(xiàng)目結(jié)構(gòu),可以:setup(
??package_dir={"":?"src"}
)
打包上傳和安裝
打包
好了,這個萬惡的setup.py我已經(jīng)寫好了,咱要發(fā)布 PyPI 了。第一步,打包成可分發(fā)的文件:
$?python?setup.py?sdist?bdist_wheel?--universal
這條命令會同時生成源代碼包(Source Distribution),和二進(jìn)制包(Binary Distribution)。當(dāng)然,大部分的 Python 發(fā)布包中并不真的包含二進(jìn)制, 只是沿用了軟件工程中的一般叫法。
其中bdist_wheel生成的二進(jìn)制包是 wheel 格式(需要安裝wheel才能打包),--universal的意思是這個二進(jìn)制包對所有 支持的 Python 版本和 ABI 都適用,「 一處打包,到處使用」,生成的文件名類似:my_package-0.1.0-py3-none-any.whl。
如果你包中有 C 擴(kuò)展, 也就是打包出來的 wheel 會真的有二進(jìn)制文件時就不能加這個 flag 了,這時生成的文件名類似:my_package-0.1.0-cp38-cp38-win_amd64.whl。
這個文件名不是亂來的,是要遵循一定規(guī)則,下載器能直接從這個文件名獲得這個包的基本信息:

上傳
可能有老的教程,讓你直接用python setup.py sdist bdist_wheel register upload打包上傳一步到位,這個方式已經(jīng)過時了不推薦使用。正確的方法應(yīng)該用twine工具:
$?twine?upload?dist/*
如果你要把上傳放到 CI 里自動執(zhí)行,最好生成一個 token 來使用,訪問 https://pypi.org/manage/account/token/ 按提示生成一個 token,使用的時候只要用命令指定下用戶名和密碼:
twine?upload?--username?__token__?--password?${{?secrets.PYPI_TOKEN?}}?dist/*
安裝
把包上傳到 PyPI 以后,pip install my-package的時候是怎么安裝的呢?
訪問
https://pypi.org/simple/my-package,解析所有鏈接若是 whl 文件,判斷是否與當(dāng)前 Python 版本、ABI、平臺適配,加入到候選列表
從標(biāo)簽中讀取
data-requires-python屬性,判斷是否與當(dāng)前 Python 版本兼容,加入候選列表若是源代碼包,直接加入候選列表
最終在候選列表中優(yōu)先選擇 whl 文件為待安裝的包,將包下載到本地,候選包的選擇可以由pip install的--only-binary和--no-binary選項(xiàng)控制。
現(xiàn)在準(zhǔn)備安裝了,如果待安裝的是 whl,那就非常簡單,直接解壓(whl 文件是一種 zip 格式),放到目標(biāo)目錄即可,解壓后產(chǎn)生的文件除了代碼或二進(jìn)制以外,還會包含一個my_package-0.1.0.dist-info/目錄,包含這個包的元數(shù)據(jù)信息,比如有哪些文件、文件 hash 值、entry_points 等等。
如果待安裝的文件是源代碼包,那么需要把這個壓縮包解壓到一個臨時目錄,根據(jù)包指定的方式編譯構(gòu)建,生成 whl 文件,再用 whl 安裝同樣的方法放到目標(biāo)目錄中。而這個指定的編譯方式,在 PEP 517 提案之前,是調(diào)用python setup.py install命令。在 PEP 517 發(fā)布之后,則由 PEP 517 的 build backend 控制。
setuptools 不再是唯一的選擇
PEP 517 的內(nèi)容簡單來說,就是在項(xiàng)目根目錄下的pyproject.toml定義了兩個特殊屬性(注:其實(shí)還有第三個屬性backend-path,當(dāng)你的 backend 是在本地時使用。):
[build-system]
requires?=?["setuptools?>=?40.8.0",?"wheel"]
build-backend?=?"setuptools.build_meta:__legacy__"
上面這個就是setuptools的 PEP 517 的配置,這樣可以讓老的項(xiàng)目,能直接用 PEP 517 的方式構(gòu)建。如果你的項(xiàng)目中并沒有pyproject.toml文件,pip能自動填充為此缺省配置。其中requires意為這個 backend 依賴的包列表,build-backend則為 backend 的具體位置。這個 backend 需要實(shí)現(xiàn)幾個約定的接口:
get_requires_for_build_wheel,構(gòu)建 wheel 需要的依賴列表,這個一般沒有特殊要求都是空get_requires_for_build_sdist,構(gòu)建 sdist 需要的依賴列表,同上prepare_metadata_for_build_wheel,生成一個 wheel 要用的dist-info/文件夾build_wheel,生成 wheel 文件build_sdist,生成 sdist 文件
有了這些接口,pip以及其他可能的 frontend 就能從源代碼構(gòu)建一個 wheel 出來。因此,pyproject.toml必須被包含在源代碼包中。
有了 PEP 517 的協(xié)議規(guī)范以后,backend 和 frontend 就能自由組合,不再是非setuptools不可了,實(shí)現(xiàn)了 PEP 517 的 backend 有:
Poetry-core
Flit-core
pdm-pep517
所以我可以不用寫 setup.py 了
setup.py作為一個元數(shù)據(jù)的定義格式是有問題的:
必須由 Python 運(yùn)行,無法靜態(tài)解析
由于第 1 點(diǎn),有注入惡意代碼的操作可行性
所以需要指定一個元數(shù)據(jù)的配置格式,這個格式規(guī)范最近也定下來了,它就是 PEP 621,也是使用pyproject.toml來定義的。而且,PDM已經(jīng)支持這個配置格式了,僅此一家。
閱讀鏈接
Python Packaging User Guide (https://packaging.python.org/)
setuptools 文檔 (https://setuptools.readthedocs.io/en/latest/)
Python 包構(gòu)建接口 - PEP 517
Wheel 包格式 - PEP 427
Python 包元數(shù)據(jù)格式 - PEP 621
Snake bites: Beware malicious Python libraries (https://www.infoworld.com/article/3487701/snake-bites-beware-malicious-python-libraries.html)
回復(fù)關(guān)鍵字“簡明python ”,立即獲取入門必備書籍《簡明python教程》電子版
回復(fù)關(guān)鍵字“爬蟲”,立即獲取爬蟲學(xué)習(xí)資料
python入門與進(jìn)階 每天與你一起成長 推薦閱讀
點(diǎn)贊和在看就是最大的支持??
