本文 AI 產出,尚未審核

Python 課程 – 封裝與發佈(Packaging & Distribution)

主題:wheel / dist


簡介

在 Python 生態系統中,**套件(package)**是讓程式碼得以重複使用、共享與維護的核心。
然而,僅有原始程式碼並不足以讓使用者方便地安裝與部署,特別是當套件依賴其他第三方函式庫或需要編譯 C 擴充模組時,傳統的 setup.py sdist(source distribution)往往會造成安裝時間長、環境相容性差的問題。

wheel.whl 檔案)正是為了解決這些痛點而誕生的二進位發佈格式。它是 PEP 427 所定義的「通用二進位套件」,可以直接被 pip 安裝,省去編譯步驟、提升安裝速度,同時保留跨平台的相容性。掌握 wheeldist 目錄的運作方式,對於想要 快速、可靠 發佈自己的套件至 PyPI(或內部私有庫)是必備技能。

本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,帶你一步步完成 wheel 的產生與發布,讓你的程式碼從開發階段無縫過渡到正式發佈。


核心概念

1. 什麼是 wheel

  • wheel 是一種 二進位發佈檔(Binary Distribution),副檔名為 .whl
  • 它包含已編譯好的 Python bytecode、C 擴充模組(若有)以及所有元資料(metadata),可直接被 pip install 使用。
  • 與傳統的 sdist(source distribution,通常是 .tar.gz)不同,wheel 不需要在安裝時再執行 setup.py build,因此 安裝速度通常快 5~10 倍

2. dist 目錄的角色

在使用 setuptoolspoetry 打包時,所有產出的發佈檔案(.whl.tar.gz)都會放在專案根目錄下的 dist/ 資料夾。

  • dist/產物(artifact) 的集中管理點,方便上傳至 PyPI、測試環境或內部 Artifactory。
  • 保持 dist/ 的乾淨(即每次重新打包前先清除舊檔)能避免意外上傳錯誤版本。

3. setup.cfg / pyproject.tomlsetup.py 的關係

從 Python 3.8 起,官方鼓勵使用 pyproject.toml 來宣告建置系統(PEP 517/518),setup.py 只保留必要的自訂程式碼。

  • 若你使用 setuptools,只需要在 pyproject.toml 中加入以下設定,即可使用 python -m build 產生 wheel:
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"

4. 建立 Wheel 的流程

  1. 準備專案結構src/tests/README.md 等)。
  2. 撰寫元資料pyproject.tomlsetup.cfg)。
  3. 執行 建置指令python -m build(或 python setup.py bdist_wheel)。
  4. 產生的 .whl 會出現在 dist/ 目錄。

程式碼範例

以下示範三個常見情境,從最簡單的純 Python 套件,到含有 C 擴充模組的進階範例,最後示範如何一次產生多平台 wheel(使用 cibuildwheel)。

範例 1:最簡單的純 Python 套件

目錄結構:

my_pkg/
├─ src/
│  └─ my_pkg/
│     └─ __init__.py
├─ pyproject.toml
└─ README.md

src/my_pkg/__init__.py

"""my_pkg - 一個簡單的工具函式庫"""

def hello(name: str) -> str:
    """回傳歡迎訊息"""
    return f"Hello, {name}!"

pyproject.toml

[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "my-pkg"
version = "0.1.0"
description = "示範用的純 Python 套件"
authors = [{name = "Your Name", email = "you@example.com"}]
requires-python = ">=3.8"
# 若有依賴可寫在此
# dependencies = ["requests"]

建置與驗證

# 1. 安裝 build 套件(只需要一次)
pip install --upgrade build

# 2. 產生 wheel
python -m build

# 3. 查看 dist/ 內的 .whl
ls dist/
# my_pkg-0.1.0-py3-none-any.whl

提示-none-any 表示此 wheel 為 純 Python,可在任何平台與 Python 版本上安裝。

範例 2:含 C 擴充模組的套件

假設我們要寫一個簡單的 C 函式,用於計算階乘。

目錄結構

fastfactorial/
├─ src/
│  └─ fastfactorial/
│     ├─ __init__.py
│     └─ factorial.c
├─ pyproject.toml
└─ README.md

src/fastfactorial/factorial.c

#include <Python.h>

/* 計算 n! 的遞迴實作 */
static long long fact(long long n) {
    return (n <= 1) ? 1 : n * fact(n - 1);
}

/* Python 包裝函式 */
static PyObject* py_fact(PyObject* self, PyObject* args) {
    long long n;
    if (!PyArg_ParseTuple(args, "L", &n))
        return NULL;
    return PyLong_FromLongLong(fact(n));
}

/* 方法表 */
static PyMethodDef FastFactMethods[] = {
    {"fact", py_fact, METH_VARARGS, "Calculate factorial of n"},
    {NULL, NULL, 0, NULL}
};

/* 模組定義 */
static struct PyModuleDef fastfactmodule = {
    PyModuleDef_HEAD_INIT,
    "fastfactorial",   /* name of module */
    "Fast factorial implemented in C",
    -1,
    FastFactMethods
};

PyMODINIT_FUNC PyInit_fastfactorial(void) {
    return PyModule_Create(&fastfactmodule);
}

src/fastfactorial/__init__.py

from .fastfactorial import fact   # 直接匯入 C 擴充函式

pyproject.toml(使用 setuptoolsExtension

[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "fastfactorial"
version = "0.2.0"
description = "使用 C 擴充加速階乘計算"
authors = [{name = "Your Name", email = "you@example.com"}]
requires-python = ">=3.8"

[tool.setuptools]
packages = ["fastfactorial"]
package-dir = {"" = "src"}

[tool.setuptools.ext-modules]
# 這裡直接使用 Python 語法描述 Extension
fastfactorial = {sources = ["src/fastfactorial/factorial.c"]}

注意pyproject.toml 中的 ext-modules 仍屬於 實驗性 功能,若遇到建置失敗,可改回傳統的 setup.py 方式:

# setup.py (備用)
from setuptools import setup, Extension

setup(
    name="fastfactorial",
    version="0.2.0",
    packages=["fastfactorial"],
    package_dir={"": "src"},
    ext_modules=[Extension("fastfactorial.fastfactorial", ["src/fastfactorial/factorial.c"])],
)

建置與測試

# 產生 wheel(會依據當前平台產生對應的 .whl)
python -m build

# 安裝到本機環境測試
pip install dist/fastfactorial-0.2.0-cp311-cp311-win_amd64.whl   # 依平台不同而變

# 測試
python -c "import fastfactorial; print(fastfactorial.fact(10))"
# -> 3628800

範例 3:使用 cibuildwheel 同時產出多平台 wheel

在 CI(GitHub Actions、GitLab CI)中,我們往往希望一次建置 Linux、macOS、Windows 三大平台的 wheel,然後上傳至 PyPI。cibuildwheel 提供了簡潔的指令列工具。

pyproject.toml(保持與範例 2 相同的設定)

GitHub Actions 工作流程 (.github/workflows/wheel.yml)

name: Build & Publish Wheels

on:
  push:
    tags: ["v*"]   # 只在打 Tag 時執行

jobs:
  build_wheels:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]

    steps:
      - uses: actions/checkout@v3

      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: "3.11"

      - name: Install build tools
        run: |
          python -m pip install --upgrade pip
          pip install build cibuildwheel==2.16.5

      - name: Build wheels
        run: |
          python -m cibuildwheel --output-dir dist

      - name: Upload wheels as artifact
        uses: actions/upload-artifact@v3
        with:
          name: wheels-${{ matrix.os }}
          path: dist/*.whl

執行結果會在每個平台產生對應的 .whl,例如:

  • fastfactorial-0.2.0-cp311-cp311-manylinux_2_17_x86_64.whl(Linux)
  • fastfactorial-0.2.0-cp311-cp311-macosx_10_9_x86_64.whl(macOS)
  • fastfactorial-0.2.0-cp311-cp311-win_amd64.whl(Windows)

之後再使用 twine 上傳:

pip install twine
twine upload dist/*

常見陷阱與最佳實踐

常見問題 為何會發生 解決方式 / Best Practice
Wheel 只支援單一平台 C 擴充模組在不同 OS/CPU 上需要重新編譯。 為每個目標平台分別建置 wheel(使用 CI + cibuildwheel),或提供 pure‑Python 版作 fallback。
dist/ 目錄遺留舊版檔案 手動刪除不徹底或忘記清理。 在建置前執行 rm -rf dist/ build/(Linux/macOS)或 rmdir /s /q dist build(Windows)。
setup.cfgpyproject.toml 設定衝突 同時寫入兩者的 metadata,導致 pip install 時警告。 盡量只使用 pyproject.toml(PEP 517),或把 setup.cfg 的設定搬到 pyproject.toml
缺少 wheel 套件導致建置失敗 只安裝了 setuptools pip install --upgrade setuptools wheel,或在 pyproject.tomlrequires 中加入 "wheel"
在 Windows 上編譯 C 擴充失敗 缺少 Visual C++ Build Tools。 安裝「Microsoft C++ Build Tools」或使用 conda 提供的預編譯二進位套件。

最佳實踐總結

  1. 使用 pyproject.toml 宣告建置需求,保持專案結構簡潔。
  2. 每次發布前清空 dist/,避免誤上傳舊版。
  3. 針對平台依賴的套件提供 pure‑Python 後備方案,提升跨平台可用性。
  4. 在 CI 中自動化多平台 wheel 建置,減少手動錯誤。
  5. 使用 twine 安全上傳,並在上傳前用 twine check 檢查 metadata。

實際應用場景

  1. 企業內部私有套件庫

    • 使用 wheel 可以在內部的 Artifactory 或 Nexus 中快速分發已編譯好的套件,減少每台機器的建置時間。
  2. 開源函式庫發佈

    • 大多數熱門的 PyPI 套件(如 numpy, pandas, requests)皆提供多平台 wheel,讓使用者在安裝時免除編譯成本。
  3. CLI 工具的單檔發佈

    • 透過 wheel 搭配 entry_points.console_scripts,可以一次打包成可直接執行的指令列工具,使用者只要 pip install mycli 即可得到可執行檔。
  4. 嵌入式或資料科學環境

    • 在 Jupyter Notebook 或 Google Colab 中安裝自訂套件時,若提供 wheel,安裝速度可從數十秒縮短至數秒,提升開發者體驗。

總結

  • wheel 是 Python 發佈的事實標準,提供 二進位、即時安裝 的便利性。
  • dist/ 資料夾是所有建置產物的集中點,保持乾淨、版本一致是成功發布的關鍵。
  • 透過 pyproject.toml + setuptools(或 poetry)即可輕鬆產生 wheel;若套件包含 C 擴充,則必須針對每個平台分別建置。
  • 使用 CI(cibuildwheel) 產出多平台 wheel,搭配 twine 上傳至 PyPI,實現自動化、可靠的發佈流程。

掌握上述概念與實作步驟,你就能從「寫程式」順暢過渡到「分發套件」的完整生命週期,讓自己的程式碼以最專業、最易用的方式服務給廣大的 Python 使用者。祝你打包順利、發佈成功!