Python 生態系蓬勃發展,仰賴完善的套件管理機制。理解套件的發行與安裝流程,對於 Python 開發者至關重要。本文將探討 sdist 和 wheel 兩種常見的套件發行格式,並比較它們的優缺點。同時,文章也將介紹如何使用 poetry、flit 和 setuptools 等工具來建構和發布套件,以及如何選擇和使用私有套件儲存函式庫。此外,版本控制在套件開發中扮演著重要的角色,本文將示範如何使用 git 進行版本管理。最後,文章將強調測試的重要性,並以實際案例說明如何使用 pytest 框架撰寫和執行測試,確保套件的品質和穩定性。
Python 套件結構與散佈
在前面的章節中,我們討論瞭如何建立和測試 Python 套件。現在,我們將探討如何將套件散佈給使用者。Python 套件的散佈涉及幾個步驟,包括建立散佈檔案、將套件上傳到 PyPI,以及使用者如何下載和安裝套件。
套件安裝
要安裝套件,需要產生兩個目錄:
{package}:包含套件原始碼檔案的目錄(即模組和子套件)。{package}-{version}.dist-info:包含有關套件資訊的目錄,例如包含套件作者和支援的 Python 版本等資訊的中繼資料檔案(METADATA)、授權檔案(LICENSE)、指定用於安裝套件的工具的檔案(INSTALLER)等。
這些檔案在 PEP 427中有詳細描述。當您使用像 pip 這樣的安裝工具安裝套件時,上述目錄會被複製到 Python 安裝的 site-packages/ 目錄中,這是 Python 預設搜尋套件的位置。
程式碼範例:檢查 sys.path
>>> import sys
>>> sys.path
['',
'/opt/miniconda/base/envs/pycounts/lib/python39.zip',
'/opt/miniconda/base/envs/pycounts/lib/python3.9',
'/opt/miniconda/base/envs/pycounts/lib/python3.9/lib-dynload',
'/opt/miniconda/base/envs/pycounts/lib/python3.9/site-packages']
內容解密:
sys.path是一個列表,包含了 Python 搜尋模組的路徑。- 當您安裝一個套件時,它通常會被安裝在
site-packages/目錄下。 - 在這個範例中,Python 是安裝在 Miniconda 環境中,並且啟用了名為
pycounts的虛擬環境。
散佈格式:sdist 和 wheel
要提供必要的目錄,有兩種選擇:
- 原始碼散佈(sdist):建立一個包含所有套件原始碼、中繼資料和建立指令的壓縮檔。使用者需要下載、解壓縮,並按照建立指令建立
{package}和{package}-{version}.dist-info目錄。 - wheel:在開發者的機器上建立
{package}和{package}-{version}.dist-info目錄,然後將它們壓縮成一個單一檔案。使用者只需下載 wheel 檔案並將其內容解壓縮到site-packages/目錄中,無需建立步驟。
為什麼 wheel 是首選?
wheel 是 Python 套件的首選散佈格式,因為它簡化了安裝過程。使用者只需下載 wheel 檔案並將其內容複製到正確的位置即可。
sdists 的必要性
雖然 wheel 是首選格式,但 sdists 仍然是必要的。某些 Python 套件包含用其他語言(如 C/C++)編寫的擴充功能,這些語言需要編譯。編譯是平台特定的,因此開發者需要為每個想要支援的平台生成一個 wheel 檔案。如果沒有可用的 wheel 檔案,使用者可以從 sdist 安裝套件。
軟體套件的發行與安裝
在 Python 的套件管理中,發行(distribution)和安裝是至關重要的環節。Python 套件可以透過不同的格式進行發行,其中最常見的有原始碼發行版(sdist)和輪子檔(wheel)。這兩種格式各有其特點和適用場景。
輪子檔與原始碼發行版
輪子檔是一種預先構建的套件格式,可以直接安裝而無需編譯。相較之下,原始碼發行版則需要使用者自行編譯安裝。輪子檔的命名遵循特定的慣例(詳見 PEP 427),包含了支援的平台資訊。例如,流行的 numpy 套件由於包含 C 語言擴充套件,因此其輪子檔是特定於平台的。在 PyPi 上,numpy 提供了多個平台的輪子檔,以及一個原始碼發行版。
大多數純 Python 套件無需擔心生成特定平台的輪子檔,通常只需生成一個通用輪子檔(compatible with Python 2 and 3)或純 Python 輪子檔。建構工具(如 poetry)會自動處理輪子檔的生成。
建構原始碼發行版和輪子檔
建構輪子檔的過程涉及多個步驟:開發者將套件原始碼建構成原始碼發行版,然後由開發者或使用者從原始碼發行版建構輪子檔,最後由使用者安裝輪子檔。像 poetry、flit 和 setuptools 這樣的套件管理工具提供了建構原始碼發行版和輪子檔的功能。
以 poetry 為例,其 pyproject.toml 檔案中的 [build-system] 部分指定了建構所需的工具和函式。例如:
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
這指定了 poetry-core 是建構所需的工具,並且建構函式位於 poetry.core.masonry.api 中。PEP 517 和 PEP 518 引入了指定建構工具的功能,以移除對舊工具的依賴。
建構流程式碼範例
# poetry.core.masonry.api 中的建構函式範例
def build_wheel(wheel_directory, config_settings=None, metadata_directory=None):
# 建構輪子檔的邏輯
pass
def build_sdist(sdist_directory, config_settings=None):
# 建構原始碼發行版的邏輯
pass
內容解密:
build_wheel函式:負責將套件建構成輪子檔,並將其放置在指定的wheel_directory中。build_sdist函式:負責將套件原始碼建構成原始碼發行版,並將其放置在指定的sdist_directory中。config_settings和metadata_directory引數:提供了額外的組態選項和後設資料,用於控制建構過程。- 建構邏輯:具體的建構邏輯涉及檔案處理、依賴管理等,這些細節由 poetry-core 實作。
套件管理工具
本文重點介紹了使套件管理變得便捷的工具,如 poetry 和 flit。這些工具透過抽象化底層細節,使開發者能夠專注於編寫程式碼。poetry 由單一的 pyproject.toml 檔案組態,提供了直觀的命令來安裝、管理依賴、建構和發布套件。
flit 是另一個現代化的套件管理工具,與 poetry 相似,但它不自動管理依賴,需要手動在 pyproject.toml 中新增依賴及其版本規範。
對於包含非 Python 程式碼的進階套件,setuptools 是首選。setuptools 長期以來一直是 Python 套件的預設建構工具,因此仍被許多專案使用,但它需要更多的組態專業知識。
Python 套件建構與安裝流程
@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
skinparam rectangleBackgroundColor #F5F5F5
skinparam rectangleBorderColor #333333
skinparam arrowColor #333333
title 套件管理工具
rectangle "1. 建構原始碼發行版" as node1
rectangle "2. 建構輪子檔" as node2
rectangle "3. 安裝輪子檔" as node3
rectangle "使用 poetry 或 flit 或 setuptools" as node4
rectangle "建構 sdist 和 wheel" as node5
rectangle "管理依賴和發布" as node6
node1 --> node2
node2 --> node3
node3 --> node4
node4 --> node5
node5 --> node6
@enduml
此圖示展示了從開發者到使用者的套件建構與安裝流程,以及所涉及的建構工具和 PyPI。
圖表內容解密:
- 開發者到 sdist:開發者使用建構工具將套件原始碼封裝成原始碼發行版。
- sdist 到 wheel:從原始碼發行版進一步建構成輪子檔。
- wheel 到使用者:最終使用者安裝輪子檔以使用套件。
- 建構工具的作用:poetry、flit 和 setuptools 等工具負責建構和管理依賴,以及發布到 PyPI。
4.3 套件發行與安裝
在第三章「如何封裝 Python 套件」中,我們將 pycounts 套件釋出到 Python Package Index(PyPI),並討論了 PyPI 是主要的 Python 套件儲存函式庫。即使你從未聽說過 PyPI,但只要你曾經執行過 pip install <some-package>,就已經從那裡安裝了套件。如果你有興趣公開分享你的作品,PyPI 可能是你釋出套件的地方,但它並不是唯一的選擇。
4.3.4 套件儲存函式庫
Anaconda 和 conda-forge 儲存函式庫是 Python 套件的下一個最受歡迎的軟體儲存函式庫。這些儲存函式庫上的套件可以透過命令列使用 conda install 安裝(我們在 2.2.1 節中安裝了 conda 工具)。PyPI 和這些儲存函式庫之間的主要區別在於,它們可以託管非 Python 軟體(與僅託管 Python 軟體的 PyPI 相反),並且 conda 套件是二進位制檔案(永遠不需要從 sdist 建立套件或其依賴項)。因此,依賴於非 Python 程式碼的套件通常會釋出到 Anaconda 或 conda-forge。即使對於純 Python 的套件,開發人員有時仍會建立 conda 套件並上傳到 Anaconda 或 conda-forge,以滿足使用 conda 而不是 pip 的使用者。
對於那些感興趣的人,Anaconda 提供了一個有用的教學,幫助將 PyPI 上的套件轉換為 conda 套件,但對於大多數讀者來說,建立 sdist 和 wheel 發行版並在 PyPI 上分享就足夠了。
私有儲存函式庫
在某些情況下,您可能希望將您的套件釋出到私有儲存函式庫(例如,僅供公司內部使用)。Python 套件有多種私有儲存函式庫選項。像 Anaconda、PyDist 和 GemFury 這樣的公司都是提供(通常是付費的)私有 Python 套件儲存函式庫託管的例子。您也可以在專用機器或雲端服務上設定自己的伺服器。您也可以選擇將您的套件託管在 GitHub(或同等平台)上,並放棄釋出到專用的軟體儲存函式庫。pip install 支援直接從您有存取許可權的 GitHub 儲存函式庫安裝套件。
例如,我們在 3.9 節中在 GitHub 上標記了 pycounts 套件的 v0.1.0 版本。其他人現在可以使用以下命令直接從 GitHub 安裝我們的套件:
$ pip install git+https://github.com/TomasBeuzen/[email protected]
從 GitHub 安裝的適用情況
從 GitHub 安裝對於想要使用您套件的尚未在 PyPI 上發布的版本(例如開發版本)的使用者很有用,或者如果您想將您的套件託管在私有儲存函式庫中並僅與少數合作者分享。但是,一般來說,我們不建議使用 GitHub 向廣大使用者分享 Python 套件,因為絕大多數 Python 使用者不會從 GitHub 安裝套件,而像 PyPI 這樣的專用軟體儲存函式庫提供了更好的可發現性、安裝便利性和真實性認證。
4.4 版本控制
在 4.2.6 節中,我們對 pycounts 套件進行了重要的更改,新增了一個新的 datasets 模組和一些範例資料。我們將在第 7 章「釋出和版本控制」中發布我們套件的新版本,該版本包含了這一更改。因此,如果您正在跟著一起建立 pycounts 套件並使用版本控制,請使用以下命令將這些更改提交到您的本地和遠端儲存函式庫。如果您沒有建立 pycounts 套件或沒有使用版本控制,可以跳到下一章。
$ git add src/pycounts/datasets.py src/pycounts/data
$ git commit -m "feat: add example data and datasets module"
$ git push
版本控制的重要性
版本控制是跟蹤程式碼變更的重要工具,能夠幫助開發者管理和協作專案。
第 5 章:測試
測試是 Python 套件開發的重要組成部分,但由於被認為會增加額外的工作量而經常被忽視。然而,事實恰恰相反!在您的工作流程中引入正式的自動化測試可以帶來多個好處:
- 更少的錯誤:您從開發者和使用者的角度明確地構建和測試您的程式碼。
- 更好的程式碼結構:撰寫測試會迫使您組織和構建程式碼,使其更容易測試和理解。
- 更簡易的開發:正式的測試將幫助您和其他人在不破壞現有功能的情況下新增功能。
5.1 測試工作流程
一般來說,測試的目標是檢查您的程式碼是否產生了您預期的結果。您可能已經在當前工作流程中對程式碼進行了非正式的測試。在典型的開發過程中,我們會撰寫程式碼,在 Python 環境中執行它以檢視是否如預期般運作,然後進行修改,重複這個過程。這種方式有時被稱為「手動測試」或「探索性測試」,在程式碼開發的早期階段很常見。但是,當您開發打算封裝、重用並可能與他人分享的程式碼時,您需要以更正式和可重複的方式進行測試。
在 Python 中,測試通常使用 assert 陳述式撰寫,該陳述式檢查給定表示式的真實性,如果表示式為假,則傳回使用者定義的錯誤訊息。為了演示這個過程,假設我們想要建立一個名為 count_letters() 的函式,用於計算字串中的字母數量。作為該函式的第一個版本,我們提出了以下程式碼:
count_letters() 函式範例
def count_letters(s):
return len(s)
內容解密:
count_letters()函式目前僅傳回輸入字串的長度,這可能不是我們預期的結果,因為它沒有區分字母和其他字元。- 要改進這個函式,我們需要新增邏輯來計算字串中的字母數量,而忽略其他字元。
- 可以透過遍歷字串並檢查每個字元是否為字母來實作這一點。
def count_letters(s):
return sum(1 for c in s if c.isalpha())
內容解密:
- 改進後的
count_letters()函式使用生成器表示式遍歷輸入字串s中的每個字元c。 c.isalpha()方法檢查字元c是否為字母。如果是,則計數器加一。sum()函式將生成器表示式產生的所有計數值加總,從而給出字串中的字母總數。
透過這種方式,我們可以不斷改進和測試我們的程式碼,以確保它按照預期工作。
測試流程與結構
在軟體開發中,測試是一個不可或缺的環節。本章節將介紹測試的基本流程、結構以及如何使用pytest框架進行測試。
測試流程
測試流程通常包括以下步驟:
- 撰寫測試:根據程式碼的功能,撰寫對應的測試案例。
- 撰寫程式碼:根據測試案例,撰寫對應的程式碼。
- 執行測試:執行測試案例,檢查程式碼是否正確。
- 重構程式碼:根據測試結果,重構程式碼以提高其品質。
- 重複:重複上述步驟,直到程式碼滿足所有測試案例。
這個流程被稱為測試驅動開發(Test-Driven Development, TDD)。TDD是一種軟體開發方法,強調在撰寫程式碼之前,先撰寫測試案例。這種方法可以幫助開發者更好地理解程式碼的需求、減少錯誤並提高開發效率。
例項:計算字串中的字母數量
def count_letters(text):
"""計算字串中的字母數量"""
return len(text.replace(" ", ""))
上述程式碼使用.replace()方法移除字串中的空格,然後使用len()函式計算字母數量。我們可以撰寫測試案例來驗證這個函式的正確性:
def test_count_letters():
"""測試計算字串中的字母數量"""
assert count_letters("Hello") == 5, "'Hello' 應該有 5 個字母"
assert count_letters("Hello world") == 10, "'Hello world' 應該有 10 個字母"
內容解密:
count_letters函式接受一個字串引數text,並傳回該字串中的字母數量。- 使用
text.replace(" ", "")移除字串中的空格,以確保正確計算字母數量。 - 使用
len()函式計算移除空格後的字串長度,即字母數量。
測試結構
pytest是一個流行的Python測試框架。要使用pytest,需要遵循以下結構:
- 測試函式:測試函式以
test_為字首,包含一個或多個斷言陳述式,用於檢查程式碼是否產生預期的結果或引發特定的錯誤。 - 測試檔案:測試檔案以
test_*.py或*_test.py命名,通常放在專案根目錄下的tests/目錄中。
pytest使用範例
假設我們有一個名為pycounts的專案,目錄結構如下:
pycounts/
├── src/
│ └── pycounts.py
└── tests/
├── einstein.txt
└── test_pycounts.py
在test_pycounts.py中,我們可以撰寫測試函式:
from pycounts.pycounts import count_words
from collections import Counter
def test_count_words():
"""測試從檔案中計數單詞"""
expected = Counter({'insanity': 1, 'is': 1, 'doing': 1,
'the': 1, 'same': 1, 'thing': 1,
'over': 2, 'and': 2, 'expecting': 1,
'different': 1, 'results': 1})
actual = count_words("tests/einstein.txt")
assert actual == expected, "Einstein 引語計數錯誤!"
內容解密:
test_count_words函式測試count_words函式是否正確計數檔案中的單詞。- 使用
Counter物件比較預期結果和實際結果。 - 使用
assert陳述式檢查實際結果是否符合預期。
要執行測試,需要先安裝pytest作為開發依賴項:
$ poetry add --dev pytest
然後,在專案根目錄下執行以下命令:
$ pytest tests/
pytest將自動發現並執行tests/目錄下的測試檔案中的測試函式。