本文 AI 產出,尚未審核

Python – 模組與套件(Modules & Packages)

建立自訂模組與封裝


簡介

在 Python 的開發流程中,模組(module)套件(package) 是組織程式碼、提升可重用性與維護性的基礎。當專案規模逐漸擴大時,若所有程式都寫在同一個檔案裡,會導致程式難以閱讀、測試與除錯。透過自行建立模組與封裝,我們可以把相關功能切割成獨立單元,讓團隊成員只需要關注自己負責的模組,且能夠輕鬆在不同專案間共享程式碼。

本篇文章將從 建立自訂模組設計套件結構發佈與安裝 三個核心面向說明,並提供多個實作範例、常見陷阱與最佳實踐,幫助讀者在實務上快速上手、打造乾淨且可維護的 Python 專案。


核心概念

1️⃣ 什麼是模組?

模組 就是一個 .py 檔案,裡面可以包含函式、類別、變數、甚至是其他模組的匯入。只要在其他程式中使用 importfrom … import …,就能取得模組內的資源。

範例 1:簡單的工具模組 math_utils.py

# math_utils.py
"""提供常用的數學運算輔助函式"""

def is_prime(n: int) -> bool:
    """判斷正整數 n 是否為質數"""
    if n <= 1:
        return False
    for i in range(2, int(n ** 0.5) + 1):
        if n % i == 0:
            return False
    return True

def factorial(n: int) -> int:
    """計算 n 的階乘,使用遞迴實作"""
    if n < 0:
        raise ValueError("階乘只接受非負整數")
    return 1 if n == 0 else n * factorial(n - 1)

使用方式:

# main.py
import math_utils

print(math_utils.is_prime(17))   # True
print(math_utils.factorial(5))   # 120

重點:只要把 math_utils.py 放在 Python 可搜尋的路徑(同目錄或 PYTHONPATH),即可直接匯入。


2️⃣ 套件的概念與結構

當需要管理多個相關模組時,我們會把它們放在同一個資料夾內,並在資料夾根目錄加入 __init__.py(即使是空檔案),讓 Python 把該資料夾視為一個 套件。套件可以有子套件,形成層次化的命名空間。

範例 2:建立 datascience 套件

datascience/
│
├── __init__.py
├── preprocessing/
│   ├── __init__.py
│   └── cleaner.py
└── modeling/
    ├── __init__.py
    └── linear.py

cleaner.py

# datascience/preprocessing/cleaner.py
def drop_missing(df, columns=None):
    """刪除指定欄位的缺失值,若未指定則全部欄位"""
    return df.dropna(subset=columns)

linear.py

# datascience/modeling/linear.py
from sklearn.linear_model import LinearRegression

def train_linear(X, y):
    """訓練簡單線性回歸模型,回傳訓練好的 estimator"""
    model = LinearRegression()
    model.fit(X, y)
    return model

__init__.py(根目錄):

# datascience/__init__.py
"""DataScience 套件的入口文件"""

# 讓使用者可以直接從 datascience 匯入子模組
from .preprocessing.cleaner import drop_missing
from .modeling.linear import train_linear

__all__ = ["drop_missing", "train_linear"]

使用方式:

# demo.py
import datascience as ds
import pandas as pd

df = pd.DataFrame({"A": [1, None, 3], "B": [4, 5, None]})
clean_df = ds.drop_missing(df, columns=["A"])
print(clean_df)

# 假設 X, y 已經準備好
model = ds.train_linear(X, y)
print(model.coef_)

技巧:在 __init__.py 中使用 __all__ 控制 from package import * 時的匯出項目,並可將常用函式重新導向,提升使用者體驗。


3️⃣ 相對匯入 vs 絕對匯入

在套件內部,相對匯入from .module import …)可避免因套件名稱變動而產生的匯入錯誤;絕對匯入from package.module import …)則在跨套件時較為直觀。建議:

  • 套件內部使用 相對匯入,保持模組間耦合度低。
  • 套件外部使用 絕對匯入,讓使用者一眼看出來源。

範例 3:在子套件內使用相對匯入

# datascience/modeling/__init__.py
# 只匯出 linear 模組
from .linear import train_linear

__all__ = ["train_linear"]

4️⃣ 建立可發佈的套件(Packaging)

若想把自訂套件分享給同事或發布到 PyPI,需要依照 Python Packaging 的標準結構準備 setup.cfgpyproject.tomlsetup.py。以下示範最簡單的 pyproject.toml 設定。

範例 4:pyproject.toml

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

[project]
name = "datascience"
version = "0.1.0"
description = "A lightweight data‑science helper package"
authors = [{name = "Your Name", email = "you@example.com"}]
license = {text = "MIT"}
readme = "README.md"
requires-python = ">=3.8"
dependencies = [
    "pandas>=2.0",
    "scikit-learn>=1.3"
]

[project.urls]
Homepage = "https://github.com/yourname/datascience"

建立套件:

# 1. 產生分發檔案
python -m build

# 2. 本機安裝測試
pip install dist/datascience-0.1.0-py3-none-any.whl

提示:使用 buildpip install build)可一次產生 sdistwheel,符合現代 Python 打包慣例。


5️⃣ 單元測試與持續整合

自訂模組與套件在開發過程中,測試 是必不可少的。利用 pytest 撰寫測試檔,並在 pyproject.toml 中加入測試相關設定,可在 CI(如 GitHub Actions)自動執行。

範例 5:簡易測試 test_math_utils.py

# tests/test_math_utils.py
import pytest
from datascience.math_utils import is_prime, factorial

def test_is_prime():
    assert is_prime(2) is True
    assert is_prime(15) is False
    assert is_prime(1) is False

def test_factorial():
    assert factorial(0) == 1
    assert factorial(5) == 120
    with pytest.raises(ValueError):
        factorial(-3)

.github/workflows/python-test.yml 中設定 CI:

name: Python package test

on: [push, pull_request]

jobs:
  test:
    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 dependencies
        run: |
          python -m pip install --upgrade pip
          pip install .[test]   # 需要在 pyproject 中定義 extras_require
      - name: Run tests
        run: pytest

常見陷阱與最佳實踐

陷阱 說明 解決方式
模組名稱與標準庫衝突 例如自行建立 json.py,會遮蔽內建的 json 套件。 盡量避免使用與標準庫同名的檔案或套件,或在 __init__.py 中加入 __all__ 明確匯出。
相對匯入錯誤 在直接執行子模組 (python submodule.py) 時,from . import … 會失敗。 使用 python -m package.module 方式執行,或在測試時以套件根目錄作為工作目錄。
缺少 __init__.py 沒有此檔案的資料夾不會被視為套件,導致匯入失敗。 確保每層資料夾都包含(哪怕是空的)__init__.py
硬編碼路徑 使用絕對檔案路徑會在不同環境下失效。 使用 import pathlib; Path(__file__).parent / "data.csv" 取得相對路徑。
依賴未列入 install_requires 發佈套件時缺少必要的第三方庫,安裝者會遇到 ImportError pyproject.tomlsetup.cfg 中正確列出所有依賴。

最佳實踐

  1. 遵循 PEP 8:檔名全小寫、使用底線分隔(my_module.py)。
  2. 使用 __all__ 控制公開 API,避免不小心暴露內部實作。
  3. 文件化:在每個模組、函式加上 docstring,並在 README.md 中提供使用範例。
  4. 版本控制:使用語意化版本(MAJOR.MINOR.PATCH),在發佈前更新 CHANGELOG.md
  5. 自動化測試:結合 pytesttox 或 GitHub Actions,確保每次提交不會破壞相依性。

實際應用場景

場景 為何需要自訂模組/套件 可能的實作
資料前處理管線 多個專案共用清理、特徵工程程式碼。 建立 preprocess 套件,提供 clean_missing()、encode_categorical() 等函式。
機器學習模型管理 同一組模型在不同服務中重複使用。 modeling 套件封裝訓練、評估、序列化(joblib.dump)的流程。
內部工具集合 團隊需要統一的 CLI、日誌、設定讀取方式。 utils 套件內含 logger.py、config.py、cli.py,透過 entry_points 建立指令。
跨專案共用 API 客戶端 多個微服務都要呼叫相同的外部 API。 api_client 套件封裝 requests 呼叫、錯誤重試、回傳模型。
教育訓練平台 課程示範需要簡潔、可直接匯入的範例程式。 建立 examples 套件,裡面放置每章節的範例程式與測試。

以上情境皆可透過 模組化套件化 的方式,降低程式碼重複、提升可測試性與部署效率。


總結

  • 模組 是最小的可匯入單位,讓函式、類別與變數得以在不同檔案間共享。
  • 套件 透過資料夾結構與 __init__.py,把多個模組組織成層次化的命名空間,便於大型專案的維護。
  • 建立 自訂套件 時,應注意相對與絕對匯入的差異、避免名稱衝突、正確設定 __all__,並以 pyproject.tomlsetup.cfg 完成打包與發佈。
  • 測試文件化CI/CD 是確保套件品質的關鍵步驟。
  • 在實務上,模組與套件的良好設計能顯著提升團隊協作效率,讓程式碼更具可重用性與可擴充性。

掌握以上概念與技巧後,你就能自信地為自己的 Python 專案打造乾淨、可維護的程式碼基礎,並在需要時輕鬆將它們分享給其他開發者或部署到生產環境。祝你寫程式愉快,持續探索 Python 的無限可能!