Python – 模組與套件(Modules & Packages)
建立自訂模組與封裝
簡介
在 Python 的開發流程中,模組(module) 與 套件(package) 是組織程式碼、提升可重用性與維護性的基礎。當專案規模逐漸擴大時,若所有程式都寫在同一個檔案裡,會導致程式難以閱讀、測試與除錯。透過自行建立模組與封裝,我們可以把相關功能切割成獨立單元,讓團隊成員只需要關注自己負責的模組,且能夠輕鬆在不同專案間共享程式碼。
本篇文章將從 建立自訂模組、設計套件結構、發佈與安裝 三個核心面向說明,並提供多個實作範例、常見陷阱與最佳實踐,幫助讀者在實務上快速上手、打造乾淨且可維護的 Python 專案。
核心概念
1️⃣ 什麼是模組?
模組 就是一個 .py 檔案,裡面可以包含函式、類別、變數、甚至是其他模組的匯入。只要在其他程式中使用 import 或 from … 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.cfg、pyproject.toml 或 setup.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
提示:使用
build(pip install build)可一次產生sdist與wheel,符合現代 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.toml 或 setup.cfg 中正確列出所有依賴。 |
最佳實踐:
- 遵循 PEP 8:檔名全小寫、使用底線分隔(
my_module.py)。 - 使用
__all__控制公開 API,避免不小心暴露內部實作。 - 文件化:在每個模組、函式加上 docstring,並在
README.md中提供使用範例。 - 版本控制:使用語意化版本(
MAJOR.MINOR.PATCH),在發佈前更新CHANGELOG.md。 - 自動化測試:結合
pytest、tox或 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.toml或setup.cfg完成打包與發佈。 - 測試、文件化 與 CI/CD 是確保套件品質的關鍵步驟。
- 在實務上,模組與套件的良好設計能顯著提升團隊協作效率,讓程式碼更具可重用性與可擴充性。
掌握以上概念與技巧後,你就能自信地為自己的 Python 專案打造乾淨、可維護的程式碼基礎,並在需要時輕鬆將它們分享給其他開發者或部署到生產環境。祝你寫程式愉快,持續探索 Python 的無限可能!