英文 | The state of Python Packaging【1】python
原做 | BERNAT GABORgit
譯者 | 豌豆花下貓github
聲明 :本文得到原做者受權翻譯,轉載請保留原文出處,請勿用於商業或非法用途。ide
pip
19.0 已經於 2019 年 1 月 22 日發佈。在其功能列表中,最值得注意的是它如今支持 PEP-517,默認狀況下是支持的,若是項目的根目錄中有一個 pyproject.toml。該 PEP 於 2015 年建立,並於 2017 年被接受。儘管 pip 花了一段時間才實現它,但該版本及其後續問題卻代表,不少人根本不熟悉它。函數
若是你想了解 Python 打包(packaging)生態的現狀及未來如何演變,請繼續閱讀。咱們但願,即便上述提到的 Python 加強提案(譯註:即 PEP,關於 PEP 的介紹,請閱讀這篇文章),現在可能會引發一些不愉快,但從長遠來看,咱們將從中受益。工具
我大約在三年前加入了 Python 開源社區(儘管使用它已有 8 年之久)。從早期開始,我就據說 Python 打包有一點黑匣子的名聲。它有不少未知的內容,人們一般只複製其它項目的構建配置文件,就使用上了。性能
在嘗試更好地理解這個黑匣子,並對其進行改進的過程當中,我已經成爲了 virtualenv 和 tox 項目的維護者,偶爾也爲 setuptools 和 pip 作些貢獻。單元測試
我但願對這個主題進行詳盡的(並但願是一個較高水平的)論述,並決定將其分爲三個部分。在這第一篇文章中,我將對 Python 打包的工做方式及其所具備的打包類型進行大概介紹。在第二篇文章中,我將詳細地介紹軟件包的安裝方式,以及 PEP-517/518 是如未嘗試對其進行改進的。最後,我再專門寫另外一篇文章,以介紹在引入這些改進時,咱們吸收的一些痛苦的教訓。測試
事先聲明,我將主要關注 Python 官方的打包系統(即 pip、setuptools,所以沒有 conda 或特定於操做系統的打包程序)。ui
Marcus Cramer 攝/Unsplash--人們第一次凝視 Python 打包時的臉
爲了講這個故事,我須要先講講如何分發 Python 軟件包的故事;更具體地說,包的安裝在過去是如何運做的,以及咱們但願它在未來如何運做。
爲了有一個具體的示例,讓我介紹一下個人很棒的示例庫:pugs
。這個庫至關簡單:它只生成一個名爲 pugs 的包,僅包含一個名爲 logic 的模塊。關於 pugs,你猜對了,logic 被用於生成隨機的引號。這是一個展示爲源碼樹(source tree)的簡單示例結構(能夠在gaborbernat / pugs 【2】裏得到):
pugs-project ├── README.rst ├── setup.cfg ├── setup.py ├── LICENSE.txt ├── src │ └── pugs │ ├── __init__.py │ └── logic.py ├── tests │ ├── test_init.py │ └── test_logic.py ├── tox.ini └── azure-pipelines.yml
這裏有四類獨特的內容:
咱們的pugs
包在用戶機器的解釋器上能用,意味着什麼?在理想狀況下,一旦啓動解釋器,用戶應該可以 import 它,並調用其中的函數:
業務邏輯代碼(src 文件夾中的內容)
測試代碼(tests 文件夾和 tox.ini)
包代碼和元數據(setup.py、setup.cfg、LICENSE.txt、README.rst--請注意,咱們現在使用的是事實上的標準打包工具setuptools【3】)
有助於項目管理和維護的文件:
Python 3.7.2 (v3.7.2:9a3ffc0492, Dec 24 2018, 02:44:43) [Clang 6.0 (clang-600.0.57)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> import pugs >>> pugs.do_tell() "An enlightened pug knows how to make the best of whatever he has to work with - A Pug's Guide to Dating - Gemma Correll"
Ryan Antooa 攝/Unsplash--讓咱們開始吧,興奮!
Python 怎麼知道什麼可用或不可用?簡短的答案是,它不知道。至少不在前期知道。相反,它將嘗試加載,並動態地檢查是否可用。
它從哪裏加載?有許多可能的位置,可是在大多數狀況下,咱們說的是從文件系統的文件夾中加載。這個文件夾在哪裏呢?對於給定的模塊,能夠打印該模塊的表示(representation)來找出:
>>> import pugs >>> pugs <module 'pugs' from '/Users/bernat/Library/Python/3.7/lib/python/site-packages/pugs/__init__.py'>
你會發現文件夾的位置取決於:
可是通常來講,對於給定的 Python 解釋器,能夠經過打印出 sys.path 變量的內容,來找到可能的目錄列表,例如在個人 MacOS 上:
>>> import sys >>> print('\n'.join(sys.path)) /Library/Frameworks/Python.framework/Versions/3.7/lib/python37.zip /Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7 /Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/lib-dynload /Users/bernat/Library/Python/3.7/lib/python/site-packages /Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages
對於第三方軟件包,會是一些 site-packages 文件夾。在以上示例中,請注意哪些是在整個系統範圍內,哪些僅屬於一個特定的用戶。這些包是如何被放在此文件夾中的?它必定是由某些安裝程序放在那裏的。
下圖展現了大多數的運行狀況:
Pinho/攝--在探索新鮮事物
在安裝時,軟件包必須生成至少兩種類型的內容,以放入 site-packages 中:有關軟件包內容的元數據文件夾,其中包含 {package}-{version} .dist-info 和業務邏輯文件。
/Users/bgabor8/Library/Python/3.7/lib/python/site-packages/pugs ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-37.pyc │ └── logic.cpython-37.pyc └── logic.py /Users/bgabor8/Library/Python/3.7/lib/python/site-packages/pugs-0.0.1.dist-info ├── INSTALLER ├── LICENSE.txt ├── METADATA ├── RECORD ├── WHEEL ├── top_level.txt └── zip-safe
發行信息(dist-info)文件夾描述了該軟件包:用於安裝該軟件包的安裝程序、該軟件包所附的許可證、在安裝過程當中建立的文件、頂層 Python 軟件包是什麼、該軟件包暴露的入口等等。在PEP-427【6】 中能夠找到每一個文件的詳細說明。
咱們如何從源碼樹中得到這兩種類型的內容呢?咱們面前有兩條大相徑庭的路徑:
wheel
包。這兩個方法的區別主要在於包的編譯/構建操做發生在哪裏:在開發者的計算機上仍是在終端用戶的計算機上。若是它發生在開發者的一邊(例如在 wheel 的狀況下),則安裝過程很是輕巧。一切都已經在開發機器上完成了。用戶機器的操做僅是簡單的下載和解壓。
在本例中,咱們使用 setuptools 做爲構建器(從源碼樹生成要放入 site-packages 文件夾中的內容)。所以,爲了在用戶機器上執行構建操做,咱們須要確保在用戶機器上有合適版本的 setuptools (若是你使用的是 40.6.0 版的功能,則必須確保用戶具備該版本或大於該版本)。
要考慮的另外一種狀況是 Python 提供了從其內部訪問 C/C++ 庫的能力(在須要的地方得到額外的性能)。這樣的軟件包被稱爲 C 擴展包(C-extension packages),由於它們利用了 CPython 提供的 C 擴展 API。
此類擴展須要編譯 C/C++ 功能,才能適用與其交互的 C/C++ 庫和當前 Python 解釋器的 C-API 庫。在這些狀況下,構建操做實際上涉及到調用一個二進制編譯器,而不只僅是像純 Python 包(例如咱們的 pugs 庫)那樣,生成元數據和文件夾結構。
若是在用戶計算機上進行構建,則須要確保在構建時,有可用的正確的庫和編譯器。如今這是一項相對困難的工做,由於有些特定於平臺的二進制文件,也是經過平臺打包工具分發的。這些庫的缺失或版本不匹配一般會在構建時觸發隱祕的錯誤,使用戶感到沮喪和困惑。
所以,若是可能的話,始終選擇將 package 打包成 wheel。這將徹底避免用戶缺乏正確的構建依賴項的問題(純 Python 類型如 setuptools 或二進制類型的 C/C++ 編譯器)。即便這些構建依賴項易於配置(例如,使用純 Python 構建器--例如 setuptools),你徹底能夠避免此步驟,來節省安裝的時間。
話雖如此,仍然有兩種須要提供源碼分發的狀況(即便在你提供 wheel 的狀況下):
源碼樹(source tree)、源碼分發(source distribution)和 wheel 之間的區別:
Charles PH 攝/Unsplash--hmmm
可在此閱讀本系列的下一篇文章【7】,瞭解在安裝軟件包時會發生什麼。謝謝閱讀!
[1] The state of Python Packaging: https://www.bernat.tech/pep-517-and-python-packaging/
[2] gaborbernat / pugs: https://github.com/gaborbernat/pugs
[3] setuptools: https://pypi.org/project/setuptools
[4] PEP-370: https://www.python.org/dev/peps/pep-0370/
[5] https://pypi.org: https://pypi.org/
[6] PEP-427: https://www.python.org/dev/peps/pep-0427/%23id14#id14
[7] 下一篇文章: https://www.bernat.tech/pep-517-518/
公衆號【Python貓】, 本號連載優質的系列文章,有喵星哲學貓系列、Python進階系列、好書推薦系列、技術寫做、優質英文推薦與翻譯等等,歡迎關注哦。