Python 課程 ── 封裝與發佈(Packaging & Distribution)
主題:發佈到 PyPI
簡介
在 Python 生態系統中,PyPI(Python Package Index) 是所有第三方套件的「超市」與「倉庫」。無論是開源貢獻者還是企業內部開發者,都會透過 PyPI 讓自己的程式庫讓全世界的開發者輕鬆安裝與使用。把套件成功上傳到 PyPI,等於把你的程式碼變成「即插即用」的工具,提升可見度、建立個人或公司品牌,甚至能夠藉此獲得經濟收益(例如付費版或捐款)。
然而,從本機開發到正式上架 PyPI,涉及許多細節:套件結構、setup.cfg / pyproject.toml 的設定、版本號規則、測試發佈、依賴管理等。若忽略其中任何一步,都可能導致 安裝失敗、相依衝突,甚至影響使用者對套件的信任。本文將從零開始,手把手說明如何把一個簡單的 Python 套件發佈到 PyPI,並提供常見陷阱與最佳實踐,協助你在實務上順利完成套件發布。
核心概念
1. 套件與發佈的基本流程
| 步驟 | 目的 | 常用工具 |
|---|---|---|
| 1. 建立套件目錄結構 | 定義程式碼、測試、文件的相對位置 | 手動或 cookiecutter |
2. 撰寫 pyproject.toml / setup.cfg |
告訴打包工具套件的名稱、版本、相依等資訊 | setuptools, flit, poetry |
| 3. 建立說明文件 | 提供使用說明、安裝方式、變更日誌 | README.md, CHANGELOG.md |
| 4. 建立測試 | 確保程式碼在不同環境下正常運作 | pytest |
| 5. 產生 Distribution 檔案 | 產出 .tar.gz (source) 與 .whl (binary) |
build、twine |
| 6. 上傳至 TestPyPI | 在測試環境驗證上傳流程 | twine upload --repository testpypi |
| 7. 正式上傳至 PyPI | 讓全世界使用者安裝套件 | twine upload |
Tips:在正式上傳前,務必先在 TestPyPI(https://test.pypi.org)測試一次,避免因錯誤的
metadata直接影響正式庫。
2. 套件目錄範例
以下是一個最小可運作的套件範例 awesome_pkg:
awesome_pkg/
├── src/
│ └── awesome_pkg/
│ ├── __init__.py
│ └── core.py
├── tests/
│ └── test_core.py
├── README.md
├── LICENSE
├── pyproject.toml
└── .gitignore
src/:建議使用 src layout,可以避免在開發環境中誤 import 本地檔案。__init__.py:定義公開 API,並可在此設定套件版本。tests/:放置單元測試,使用pytest進行執行。pyproject.toml:現代打包工具的核心設定檔。
3. pyproject.toml 基本設定(使用 setuptools)
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "awesome-pkg"
version = "0.1.0"
description = "一個示範用的 Python 套件"
readme = "README.md"
requires-python = ">=3.8"
license = {text = "MIT"}
authors = [
{name = "Jane Doe", email = "jane@example.com"}
]
keywords = ["demo", "example", "awesome"]
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]
[project.urls]
Homepage = "https://github.com/janedoe/awesome-pkg"
Documentation = "https://awesome-pkg.readthedocs.io"
[project.optional-dependencies]
dev = [
"pytest>=7.0",
"twine>=4.0",
"build>=0.10",
]
[build-system]:告訴pip如何建置套件,必須至少包含setuptools與wheel。[project]:是 PEP 621 所定義的標準欄位,取代過去的setup.cfg/setup.py。[project.optional-dependencies]:提供開發者安裝的額外套件(如測試、打包工具)。
⚡ 小技巧:若你使用
poetry,只需要把pyproject.toml換成poetry格式即可,概念相同。
4. 撰寫程式碼與測試範例
(1) src/awesome_pkg/__init__.py
"""awesome_pkg - 示範用套件"""
__all__ = ["core"]
__version__ = "0.1.0"
(2) src/awesome_pkg/core.py
def add(a: int, b: int) -> int:
"""回傳兩個整數相加的結果。
>>> add(1, 2)
3
"""
return a + b
def factorial(n: int) -> int:
"""計算 n 的階乘,n 必須是非負整數。
>>> factorial(5)
120
"""
if n < 0:
raise ValueError("n 必須是非負整數")
result = 1
for i in range(2, n + 1):
result *= i
return result
(3) tests/test_core.py
import pytest
from awesome_pkg import core
def test_add():
assert core.add(3, 4) == 7
assert core.add(-1, 1) == 0
@pytest.mark.parametrize("n,expected", [
(0, 1),
(1, 1),
(5, 120),
])
def test_factorial(n, expected):
assert core.factorial(n) == expected
def test_factorial_negative():
with pytest.raises(ValueError):
core.factorial(-3)
說明:
- 使用
pytest的parametrize減少重複測試程式碼。- 透過
assert檢查函式回傳值與例外行為。
5. 建立 Distribution 檔案
在專案根目錄執行:
# 1. 建立虛擬環境(建議)
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
# 2. 安裝建置工具
pip install --upgrade build twine
# 3. 產生套件檔案
python -m build
執行完畢後,會在 dist/ 目錄得到兩個檔案:
awesome_pkg-0.1.0.tar.gz(Source Distribution, sdist)awesome_pkg-0.1.0-py3-none-any.whl(Binary Wheel)
⚡ 小提醒:若套件包含 C 擴充套件,
wheel會依平台產生對應的二進位檔案;否則使用py3-none-any的通用 wheel。
6. 上傳至 TestPyPI 與正式 PyPI
(1) 先上傳至 TestPyPI
twine upload --repository testpypi dist/*
- 需要先在 https://test.pypi.org/account/register/ 註冊帳號,並在
~/.pypirc中設定認證資訊。
(2) 測試安裝
pip install -i https://test.pypi.org/simple/ awesome-pkg
確認可以正常匯入與使用:
>>> from awesome_pkg import core
>>> core.add(2, 3)
5
(3) 正式上傳至 PyPI
twine upload dist/*
上傳成功後,即可在 https://pypi.org/project/awesome-pkg/ 查看套件資訊,使用者只要執行 pip install awesome-pkg 即可取得最新版本。
常見陷阱與最佳實踐
| 陷阱 | 可能的結果 | 解決方案 / 最佳實踐 |
|---|---|---|
| 版本號未遵循 SemVer | 使用者無法預測相容性變更,升級時出現破壞性錯誤 | 使用 MAJOR.MINOR.PATCH 規則,並在 CHANGELOG.md 清楚記錄變更 |
忘記在 pyproject.toml 中列出相依 |
安裝時缺少必要套件,程式執行失敗 | 在 project.dependencies 中列出所有 runtime 相依,optional-dependencies 用於開發或測試 |
| 檔案遺漏(如 README、LICENSE) | PyPI 顯示空白說明,或違反開源授權條款 | 確保 README.*、LICENSE、pyproject.toml 中的 readme 欄位指向正確檔案 |
| 使用了相對路徑或硬編碼路徑 | 在不同環境下找不到資源檔案 | 透過 importlib.resources(Python 3.9+)或 pkgutil 讀取套件內部檔案 |
上傳了測試用的 dev 版本 |
使用者不小心安裝了未完成的功能 | 在正式發佈前,務必刪除 dist/ 中的 *.dev* 檔案,或使用 --skip-existing 只上傳正式版 |
未使用 twine,直接 setup.py upload |
密碼以明文傳輸,安全性低 | 永遠使用 twine,它會透過 HTTPS 安全上傳,且支援 API token |
其他最佳實踐
自動化 CI/CD
- 使用 GitHub Actions、GitLab CI 或 Azure Pipelines,在每次
push或release時自動跑測試、產生 wheel、上傳至 TestPyPI,最後手動觸發正式上傳。 - 範例 Workflow(GitHub Actions):
name: Publish Package on: push: tags: - 'v*.*.*' # 只在打上版本 tag 時執行 jobs: build-and-publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.11' - name: Install build tools run: | python -m pip install --upgrade pip pip install build twine - name: Build distribution run: python -m build - name: Publish to PyPI env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} run: | twine upload dist/*- 使用 GitHub Actions、GitLab CI 或 Azure Pipelines,在每次
使用 API Token 而非密碼
- 在 PyPI 個人帳號設定頁面產生 API Token,只授權特定套件,避免全域帳號被濫用。
保持文件同步
README.md、CHANGELOG.md與pyproject.toml中的description、version必須保持一致,建議使用腳本或towncrier自動產生變更日誌。
提供多種安裝方式
- 若套件有 C 擴充,提供預編譯的 wheel,並在
README中說明如何使用pip install --no-binary :all:進行從原始碼編譯。
- 若套件有 C 擴充,提供預編譯的 wheel,並在
實際應用場景
| 場景 | 需求 | 發佈策略 |
|---|---|---|
| 內部工具庫(公司內部) | 僅限公司員工使用,需快速迭代 | 使用 私有 PyPI(如 pypiserver、devpi)或在 TestPyPI 設定私有索引;版本號可使用 0.x,但仍遵循 SemVer。 |
| 開源函式庫(公開) | 需要吸引外部開發者,維護文件與 CI | 正式上傳至 PyPI,同時在 Read the Docs 建立自動化文件,利用 GitHub Actions 完成測試、發布與自動生成 changelog。 |
| CLI 工具(命令列) | 需要 entry_points 產生可執行指令 |
在 pyproject.toml 中加入 scripts 或 gui-scripts,例如:[project.scripts] mytool = "awesome_pkg.cli:main",上傳後使用者只要 pip install awesome-pkg 即可直接呼叫 mytool。 |
| 大型科學套件(包含大量資料檔) | 需要確保資料檔隨套件一起安裝 | 使用 package-data 或 include_package_data = true,搭配 MANIFEST.in 列出要打包的非 Python 檔案(如 .csv, .json)。 |
| 跨平台二進位擴充 | 需要支援 Windows、macOS、Linux | 在 CI 中使用 cibuildwheel 產生多平台 wheel,並在 twine upload 時一次上傳所有 wheel,讓使用者能自動取得相符平台的檔案。 |
總結
將 Python 套件發佈到 PyPI 並非僅僅執行 twine upload 那麼簡單,背後牽涉到 套件結構、metadata、版本管理、測試、CI/CD 等多個層面的最佳實踐。透過本文的步驟,你已經學會:
- 建立符合 src layout 的套件目錄。
- 使用
pyproject.toml(PEP 621)定義套件資訊與相依。 - 撰寫單元測試,確保程式碼品質。
- 利用
build產生 source 與 wheel,並使用twine安全上傳。 - 在 TestPyPI 先行驗證,最後正式上傳至 PyPI。
- 避免常見陷阱,並遵守 SemVer、API Token、CI/CD 等最佳實踐。
只要依循上述流程,你的套件就能以 專業、可靠 的形象呈現在全球開發者面前,為自己的開發生涯或公司產品增添價值。祝你發佈順利,開源之路越走越寬! 🚀