本文 AI 產出,尚未審核

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) buildtwine
6. 上傳至 TestPyPI 在測試環境驗證上傳流程 twine upload --repository testpypi
7. 正式上傳至 PyPI 讓全世界使用者安裝套件 twine upload

Tips:在正式上傳前,務必先在 TestPyPIhttps://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 如何建置套件,必須至少包含 setuptoolswheel
  • [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)

說明

  • 使用 pytestparametrize 減少重複測試程式碼。
  • 透過 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/*

(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.*LICENSEpyproject.toml 中的 readme 欄位指向正確檔案
使用了相對路徑或硬編碼路徑 在不同環境下找不到資源檔案 透過 importlib.resources(Python 3.9+)或 pkgutil 讀取套件內部檔案
上傳了測試用的 dev 版本 使用者不小心安裝了未完成的功能 在正式發佈前,務必刪除 dist/ 中的 *.dev* 檔案,或使用 --skip-existing 只上傳正式版
未使用 twine,直接 setup.py upload 密碼以明文傳輸,安全性低 永遠使用 twine,它會透過 HTTPS 安全上傳,且支援 API token

其他最佳實踐

  1. 自動化 CI/CD

    • 使用 GitHub Actions、GitLab CI 或 Azure Pipelines,在每次 pushrelease 時自動跑測試、產生 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/*
    
  2. 使用 API Token 而非密碼

    • 在 PyPI 個人帳號設定頁面產生 API Token,只授權特定套件,避免全域帳號被濫用。
  3. 保持文件同步

    • README.mdCHANGELOG.mdpyproject.toml 中的 descriptionversion 必須保持一致,建議使用腳本或 towncrier 自動產生變更日誌。
  4. 提供多種安裝方式

    • 若套件有 C 擴充,提供預編譯的 wheel,並在 README 中說明如何使用 pip install --no-binary :all: 進行從原始碼編譯。

實際應用場景

場景 需求 發佈策略
內部工具庫(公司內部) 僅限公司員工使用,需快速迭代 使用 私有 PyPI(如 pypiserverdevpi)或在 TestPyPI 設定私有索引;版本號可使用 0.x,但仍遵循 SemVer。
開源函式庫(公開) 需要吸引外部開發者,維護文件與 CI 正式上傳至 PyPI,同時在 Read the Docs 建立自動化文件,利用 GitHub Actions 完成測試、發布與自動生成 changelog。
CLI 工具(命令列) 需要 entry_points 產生可執行指令 pyproject.toml 中加入 scriptsgui-scripts,例如:[project.scripts] mytool = "awesome_pkg.cli:main",上傳後使用者只要 pip install awesome-pkg 即可直接呼叫 mytool
大型科學套件(包含大量資料檔) 需要確保資料檔隨套件一起安裝 使用 package-datainclude_package_data = true,搭配 MANIFEST.in 列出要打包的非 Python 檔案(如 .csv, .json)。
跨平台二進位擴充 需要支援 Windows、macOS、Linux 在 CI 中使用 cibuildwheel 產生多平台 wheel,並在 twine upload 時一次上傳所有 wheel,讓使用者能自動取得相符平台的檔案。

總結

將 Python 套件發佈到 PyPI 並非僅僅執行 twine upload 那麼簡單,背後牽涉到 套件結構、metadata、版本管理、測試、CI/CD 等多個層面的最佳實踐。透過本文的步驟,你已經學會:

  1. 建立符合 src layout 的套件目錄。
  2. 使用 pyproject.toml(PEP 621)定義套件資訊與相依。
  3. 撰寫單元測試,確保程式碼品質。
  4. 利用 build 產生 source 與 wheel,並使用 twine 安全上傳。
  5. TestPyPI 先行驗證,最後正式上傳至 PyPI
  6. 避免常見陷阱,並遵守 SemVer、API Token、CI/CD 等最佳實踐。

只要依循上述流程,你的套件就能以 專業、可靠 的形象呈現在全球開發者面前,為自己的開發生涯或公司產品增添價值。祝你發佈順利,開源之路越走越寬! 🚀