本章比較主流的 Python 套件建構系統。

本章目標

學完本章後,你將能夠:

  1. 理解不同建構系統的設計理念
  2. 根據專案需求選擇適合的工具
  3. 在不同工具之間遷移

【總覽】建構系統生態

建構後端 vs 建構前端

 1建構前端(Frontend):使用者互動的工具
 2├── pip
 3├── build
 4└── 各工具的 CLI(poetry, hatch, etc.)
 5
 6建構後端(Backend):實際執行建構的程式
 7├── setuptools.build_meta
 8├── flit_core.buildapi
 9├── hatchling.build
10├── poetry.core.masonry.api
11└── maturin, scikit-build-core, mesonpy

主流工具比較

工具定位特點適用場景
setuptools建構後端歷史最久,功能最全一般專案、C 擴展
Flit建構後端極簡設計純 Python 小型專案
Hatch全套工具現代設計,環境管理新專案
Poetry全套工具依賴鎖定,虛擬環境應用程式開發
PDM全套工具PEP 582,快速實驗性專案

【工具一】setuptools

特點與定位

1setuptools:
2├── Python 打包的「標準」
3├── 歷史最悠久(2004 年開始)
4├── 支援所有功能(C 擴展、資料檔案等)
5├── 學習曲線較陡
6└── PEP 621 支援(61.0.0+ 版本)

基本設定

 1# pyproject.toml
 2[build-system]
 3requires = ["setuptools>=61.0"]
 4build-backend = "setuptools.build_meta"
 5
 6[project]
 7name = "my-package"
 8version = "1.0.0"
 9dependencies = ["requests"]
10
11[tool.setuptools.packages.find]
12where = ["src"]

進階設定

 1[tool.setuptools]
 2# 明確指定套件
 3packages = ["my_package", "my_package.submodule"]
 4
 5# 包含資料檔案
 6package-data = {"my_package" = ["*.json", "data/*"]}
 7
 8# 排除檔案
 9exclude-package-data = {"my_package" = ["test_*"]}
10
11# Zip 安全(是否可以作為 zip 檔案匯入)
12zip-safe = false
13
14[tool.setuptools.dynamic]
15version = {attr = "my_package.__version__"}
16readme = {file = ["README.md"]}

C 擴展支援

1# pyproject.toml
2[build-system]
3requires = ["setuptools>=61.0", "cython>=3.0"]
4build-backend = "setuptools.build_meta"
 1# setup.py(仍需要用於複雜的 C 擴展)
 2from setuptools import setup, Extension
 3from Cython.Build import cythonize
 4
 5extensions = [
 6    Extension(
 7        "my_package.fast_module",
 8        ["src/my_package/fast_module.pyx"],
 9    )
10]
11
12setup(
13    ext_modules=cythonize(extensions),
14)

常用命令

1# 建構
2python -m build
3
4# 可編輯安裝
5pip install -e .
6
7# 開發模式(含額外依賴)
8pip install -e ".[dev]"

【工具二】Flit

特點與定位

1Flit:
2├── 極簡設計
3├── 只支援純 Python 套件
4├── 不支援 C 擴展
5├── 快速建構
6└── 適合簡單函式庫

基本設定

 1# pyproject.toml
 2[build-system]
 3requires = ["flit_core>=3.4"]
 4build-backend = "flit_core.buildapi"
 5
 6[project]
 7name = "my-package"
 8version = "1.0.0"
 9description = "A simple package"
10authors = [{name = "Your Name", email = "you@example.com"}]
11dependencies = ["requests"]
12
13# Flit 自動從 __init__.py 讀取 docstring 和 version
14# 所以常常不需要設定 description 和 version
1# src/my_package/__init__.py
2"""A simple package for doing things."""
3__version__ = "1.0.0"

常用命令

 1# 安裝 flit
 2pip install flit
 3
 4# 建構
 5flit build
 6
 7# 發布
 8flit publish
 9
10# 可編輯安裝
11flit install --symlink  # Unix
12flit install --pth-file # Windows

限制

1Flit 不支援:
2├── C/C++/Rust 擴展
3├── 複雜的建構腳本
4├── 資料檔案的細粒度控制
5├── 動態版本(需要在 __init__.py 中定義)
6└── 依賴鎖定

【工具三】Hatch

特點與定位

1Hatch:
2├── 現代化設計
3├── 環境管理(類似 tox)
4├── 版本管理
5├── 腳本系統
6├── PEP 標準優先
7└── 由 PyPA 成員維護

基本設定

 1# pyproject.toml
 2[build-system]
 3requires = ["hatchling"]
 4build-backend = "hatchling.build"
 5
 6[project]
 7name = "my-package"
 8version = "1.0.0"
 9dependencies = ["requests"]
10
11[tool.hatch.build.targets.wheel]
12packages = ["src/my_package"]
13
14[tool.hatch.build.targets.sdist]
15include = ["/src", "/tests"]

環境管理

 1# 定義環境
 2[tool.hatch.envs.default]
 3dependencies = ["pytest", "pytest-cov"]
 4
 5[tool.hatch.envs.default.scripts]
 6test = "pytest {args:tests}"
 7cov = "pytest --cov=my_package {args:tests}"
 8
 9[tool.hatch.envs.lint]
10dependencies = ["ruff", "mypy"]
11
12[tool.hatch.envs.lint.scripts]
13check = ["ruff check .", "mypy src"]
14fix = "ruff check --fix ."
15
16[tool.hatch.envs.docs]
17dependencies = ["sphinx", "sphinx-rtd-theme"]
18
19[tool.hatch.envs.docs.scripts]
20build = "sphinx-build -b html docs docs/_build"

版本管理

1[tool.hatch.version]
2path = "src/my_package/__init__.py"
3
4# 或使用 hatch-vcs 從 git 標籤讀取
5# [tool.hatch.version]
6# source = "vcs"
7#
8# [tool.hatch.build.hooks.vcs]
9# version-file = "src/my_package/_version.py"

常用命令

 1# 安裝 hatch
 2pip install hatch
 3
 4# 建立新專案
 5hatch new my-package
 6
 7# 執行腳本
 8hatch run test
 9hatch run lint:check
10
11# 進入環境 shell
12hatch shell
13
14# 版本管理
15hatch version        # 顯示當前版本
16hatch version minor  # 升級 minor 版本
17hatch version 2.0.0  # 設定特定版本
18
19# 建構
20hatch build
21
22# 發布
23hatch publish

【工具四】Poetry

特點與定位

1Poetry:
2├── 依賴解析與鎖定
3├── 虛擬環境管理
4├── 發布流程整合
5├── 自己的設定格式([tool.poetry])
6├── Poetry 2.0 支援 [project] 表
7└── 適合應用程式開發

基本設定(Poetry 2.0+)

 1# pyproject.toml(Poetry 2.0 風格)
 2[build-system]
 3requires = ["poetry-core>=2.0"]
 4build-backend = "poetry.core.masonry.api"
 5
 6[project]
 7name = "my-package"
 8version = "1.0.0"
 9description = "My awesome package"
10authors = [{name = "Your Name", email = "you@example.com"}]
11dependencies = ["requests>=2.28"]
12requires-python = ">=3.8"
13
14[project.optional-dependencies]
15dev = ["pytest>=7.0", "ruff"]
16
17# Poetry 特定設定仍在 [tool.poetry]
18[tool.poetry]
19packages = [{include = "my_package", from = "src"}]
20
21[tool.poetry.group.dev.dependencies]
22pytest = "^7.0"
23ruff = "^0.1"

傳統設定(Poetry 1.x)

 1# pyproject.toml(舊風格,仍支援)
 2[tool.poetry]
 3name = "my-package"
 4version = "1.0.0"
 5description = "My awesome package"
 6authors = ["Your Name <you@example.com>"]
 7
 8[tool.poetry.dependencies]
 9python = "^3.8"
10requests = "^2.28"
11
12[tool.poetry.group.dev.dependencies]
13pytest = "^7.0"
14ruff = "^0.1"
15
16[build-system]
17requires = ["poetry-core>=1.0.0"]
18build-backend = "poetry.core.masonry.api"

依賴鎖定

 1# poetry.lock 檔案
 2# - 記錄所有依賴的精確版本
 3# - 確保團隊成員使用相同版本
 4# - 應該提交到版本控制
 5
 6# 安裝(使用 lock 檔案)
 7poetry install
 8
 9# 更新依賴
10poetry update
11
12# 新增依賴
13poetry add requests
14poetry add pytest --group dev
15
16# 移除依賴
17poetry remove requests

常用命令

 1# 安裝 poetry
 2pip install poetry
 3# 或官方推薦的安裝方式
 4curl -sSL https://install.python-poetry.org | python3 -
 5
 6# 建立新專案
 7poetry new my-package
 8poetry init  # 在現有目錄初始化
 9
10# 虛擬環境
11poetry env use python3.11
12poetry shell  # 進入虛擬環境
13
14# 執行命令
15poetry run python script.py
16poetry run pytest
17
18# 建構與發布
19poetry build
20poetry publish
21
22# 設定 PyPI token
23poetry config pypi-token.pypi <your-token>

【工具五】PDM

特點與定位

1PDM:
2├── 支援 PEP 582(__pypackages__)
3├── 快速的依賴解析
4├── 支援 PEP 621
5├── 插件系統
6└── 實驗性功能多

基本設定

 1# pyproject.toml
 2[build-system]
 3requires = ["pdm-backend"]
 4build-backend = "pdm.backend"
 5
 6[project]
 7name = "my-package"
 8version = "1.0.0"
 9dependencies = ["requests"]
10
11[tool.pdm]
12distribution = true
13
14[tool.pdm.dev-dependencies]
15dev = ["pytest", "ruff"]

常用命令

 1# 安裝 pdm
 2pip install pdm
 3
 4# 初始化專案
 5pdm init
 6
 7# 依賴管理
 8pdm add requests
 9pdm add -d pytest  # 開發依賴
10pdm remove requests
11pdm update
12
13# 執行
14pdm run python script.py
15pdm run pytest
16
17# 建構
18pdm build
19pdm publish

【選擇指南】決策流程

決策樹

 1需要選擇建構系統?
 2 3├── 需要 C/Rust 擴展?
 4│   ├── 是 → setuptools + Cython/pybind11
 5│   │        或 maturin(Rust)
 6│   └── 否 ↓
 7 8├── 需要依賴鎖定?
 9│   ├── 是 → Poetry 或 PDM
10│   └── 否 ↓
1112├── 需要環境管理?
13│   ├── 是 → Hatch 或 Poetry
14│   └── 否 ↓
1516├── 極簡專案?
17│   ├── 是 → Flit
18│   └── 否 → setuptools 或 Hatch

場景建議

1場景                         推薦工具
2─────────────────────────────────────────────────────
3純 Python 函式庫             Flit 或 Hatch
4Web 應用程式                 Poetry
5資料科學專案                 Poetry 或 PDM
6有 C 擴展的函式庫            setuptools
7Rust 擴展                    Maturin
8開源專案(多貢獻者)         Hatch 或 setuptools
9內部工具                     Poetry

遷移考量

 1從 setup.py 遷移:
 2├── 純 Python → 任何工具都可以
 3├── 有 C 擴展 → setuptools(保留部分 setup.py)
 4└── 複雜建構 → 保持 setuptools
 5
 6從 Poetry 1.x 遷移到 2.0:
 7├── 更新 poetry-core 版本
 8├── 可選:將 [tool.poetry.dependencies] 移到 [project]
 9└── 測試建構和安裝
10
11工具之間遷移:
12├── 所有現代工具都支援 PEP 621
13├── 主要差異在 [tool.xxx] 設定
14└── 依賴鎖定檔案不相容

【比較】功能對照表

核心功能

功能setuptoolsFlitHatchPoetry
PEP 621支援支援支援支援 (2.0)
C 擴展支援不支援不支援不支援
環境管理不支援不支援支援支援
依賴鎖定不支援不支援不支援支援
腳本系統不支援不支援支援支援
版本管理有限不支援支援支援
插件系統支援不支援支援支援

效能比較

1建構速度(純 Python 專案):
2Flit > Hatch > Poetry > setuptools
3
4依賴解析速度:
5PDM > Poetry > pip
6
7安裝速度:
8pip + lock file > Poetry > PDM

思考題

  1. 為什麼 Python 社群有這麼多打包工具?這是好事還是壞事?
  2. 依賴鎖定對函式庫和應用程式的重要性有什麼不同?
  3. 如果要開始一個新的開源專案,你會選擇哪個工具?為什麼?

實作練習

  1. 用 setuptools、Flit、Hatch 三種工具建立相同的簡單套件,比較設定檔的差異
  2. 使用 Poetry 建立一個有依賴鎖定的專案,模擬團隊協作場景
  3. 將一個現有的 setup.py 專案遷移到 pyproject.toml

延伸閱讀


上一章:pyproject.toml 完整指南 下一章:發布到 PyPI